EF6 - 无法为单元测试模拟 ObjectResult<T> 的 Return 值
EF6 - Cannot Mock Return Value for ObjectResult<T> for Unit Test
我在尝试进行单元测试的方法中有与此类似的代码:
return _context.usp_get_Some_Data(someStringParam).FirstOrDefault();
存储过程调用return类型:
ObjectResult<usp_get_Some_Data_Result>.
在我的单元测试中,我试图做这样的事情(使用 NUnit 和 Moq):
var procResult = new ObjectResult<usp_get_Some_Data_Result>();
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
.Returns(procResult);
但是,我无法创建 ObjectResult 的实例(这是 System.Data.Entity.Core.Objects.ObjectResult,而不是旧的 System.Data.Objects 实例)。它没有 public 无参数构造函数,但 documentation 表示它有一个受保护的构造函数。根据我的测试,他的文档似乎不正确。
我尝试过的:
我试过创建派生的 class 并在构造函数上调用 base(),我也试过使用反射(Activator.CreateInstance 并使用 NonPublic 的 BindingFlags 调用 ConstructorInfo,所有这些都失败了(从我的调试来看,该类型确实有三个私有构造函数,所有构造函数都有 3 个或更多参数,但不幸的是,弄清楚这些参数实际需要什么似乎是一项重大努力)。
我也尝试创建一个 IEnumberable 并将其转换为 ObjectResult 但转换失败。另外,我试过类似
var mockObjectResult = new Mock<ObjectResult<usp_get_Some_Data_Result>>();
几乎所有我尝试过的事情都失败了,并出现了关于默认构造函数不可用的类似错误。
问题:
有什么方法可以创建用于单元测试的 ObjectResult 实例,或者我可以创建可以成功转换为 ObjectResult 的任何其他类型吗?
也许我遗漏了什么,但你不能这样做吗:
class TestableObjectResult<T> : ObjectResult<T>
{
}
然后在你的测试中:
var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
MockObject 确实有一个受保护的构造函数,你真的不需要做任何事情来调用它,因为它没有任何参数,当你构造可测试版本时自动装配会处理它,所以我不确定你是什么意思 "calling base() on the constructor"...
如果我右键单击 ObjectResult 并 select 转到定义,文件顶部如下所示:
public class ObjectResult<T> : ObjectResult, IEnumerable<T>, IEnumerable, IDbAsyncEnumerable<T>, IDbAsyncEnumerable
{
// Summary:
// This constructor is intended only for use when creating test doubles that
// will override members with mocked or faked behavior. Use of this constructor
// for other purposes may result in unexpected behavior including but not limited
// to throwing System.NullReferenceException.
protected ObjectResult();
如前所述,我添加了这个答案来涵盖创建枚举器的内容,因此上面的内容实际上可以测试一些假数据:
在 [TestFixture] class 中,创建如下方法:
private static IEnumerator<usp_get_Some_Data_Result> GetSomeDataResultEnumerator()
{
yield return FakeSomeDataResult.Create(1, true);
yield return FakeSomeDataResult.Create(2, false);
}
如前一个答案中所提供的,这个方便的小包装器 class 允许实例化 ObjectResult:
public class TestableObjectResult<T> : ObjectResult<T> { }
如前一个答案中所提供的,在 [SetUp] 方法中:
var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
在这个新的 Mock 创建之后,将其设置为 return 枚举器:
mockObjectResult.Setup(d => d.GetEnumerator()).Returns(GetSomeDataResultEnumerator());
现在 OP 可以 return 来自 mockContext 的一些假数据,而不会在尝试获取枚举器时抛出空引用异常:
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
.Returns(mockObjectResult);
顺便说一句,
我只是使用助手 class 来构建我的假数据,以消除冗余:
public static class FakeSomeDataResult
{
public static usp_get_Some_Data_Result Create(int index)
{
return new usp_get_Some_Data_Result
{
SomeFriendlyNameProperty = string.Format("Some Data Result {0}", index),
};
}
}
如@forsvarir 所述,您可以创建一个 TestableObjectResult class 并将 GetEnumerator() 覆盖为 return 任何您想要的。
像这样:
private class TestableObjectResult : ObjectResult<Animal>
{
public override IEnumerator<Animal> GetEnumerator()
{
return new List<Animal>() { new Animal(), new Animal() }.GetEnumerator();
}
}
这是我的解决方案。
到目前为止它似乎对我有用。
public static class MoqExtentions
{
public static void SetupReturn<T>(this Mock<ObjectResult<T>> mock, T whatToReturn)
{
IEnumerator<T> enumerator = ((IEnumerable<T>) new T[] {whatToReturn}).GetEnumerator();
mock.Setup(or => or.GetEnumerator())
.Returns(() => enumerator);
}
}
现在您可以模拟 ObjectResult<T>
并调用将 ObjectResult<T>
设置为 return 的扩展方法,无论您传递给扩展方法什么。然后模拟的 ObjectResult<T>
可以作为方法调用的 return 值传递给模拟的 DbContext
方法设置。如果需要,可以将其传递给访问 ObjectResult<T>
.
的模拟 repo 方法
这里是一个使用模拟ObjectResult<string>
的测试例子
public void MockObjectResultReturn_OfString_Test()
{
// arrange
const string shouldBe = "Hello World!";
var sut = new Mock<ObjectResult<string>>();
// act
sut.SetupReturn<string>(shouldBe);
//assert
Assert.IsNotNull(sut);
Assert.IsNotNull(sut.Object);
Assert.AreEqual(shouldBe, sut.Object?.FirstOrDefault());
}
我在尝试进行单元测试的方法中有与此类似的代码:
return _context.usp_get_Some_Data(someStringParam).FirstOrDefault();
存储过程调用return类型:
ObjectResult<usp_get_Some_Data_Result>.
在我的单元测试中,我试图做这样的事情(使用 NUnit 和 Moq):
var procResult = new ObjectResult<usp_get_Some_Data_Result>();
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
.Returns(procResult);
但是,我无法创建 ObjectResult 的实例(这是 System.Data.Entity.Core.Objects.ObjectResult
我尝试过的: 我试过创建派生的 class 并在构造函数上调用 base(),我也试过使用反射(Activator.CreateInstance 并使用 NonPublic 的 BindingFlags 调用 ConstructorInfo,所有这些都失败了(从我的调试来看,该类型确实有三个私有构造函数,所有构造函数都有 3 个或更多参数,但不幸的是,弄清楚这些参数实际需要什么似乎是一项重大努力)。
我也尝试创建一个 IEnumberable
var mockObjectResult = new Mock<ObjectResult<usp_get_Some_Data_Result>>();
几乎所有我尝试过的事情都失败了,并出现了关于默认构造函数不可用的类似错误。
问题:
有什么方法可以创建用于单元测试的 ObjectResult
也许我遗漏了什么,但你不能这样做吗:
class TestableObjectResult<T> : ObjectResult<T>
{
}
然后在你的测试中:
var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
MockObject 确实有一个受保护的构造函数,你真的不需要做任何事情来调用它,因为它没有任何参数,当你构造可测试版本时自动装配会处理它,所以我不确定你是什么意思 "calling base() on the constructor"...
如果我右键单击 ObjectResult 并 select 转到定义,文件顶部如下所示:
public class ObjectResult<T> : ObjectResult, IEnumerable<T>, IEnumerable, IDbAsyncEnumerable<T>, IDbAsyncEnumerable
{
// Summary:
// This constructor is intended only for use when creating test doubles that
// will override members with mocked or faked behavior. Use of this constructor
// for other purposes may result in unexpected behavior including but not limited
// to throwing System.NullReferenceException.
protected ObjectResult();
如前所述,我添加了这个答案来涵盖创建枚举器的内容,因此上面的内容实际上可以测试一些假数据:
在 [TestFixture] class 中,创建如下方法:
private static IEnumerator<usp_get_Some_Data_Result> GetSomeDataResultEnumerator()
{
yield return FakeSomeDataResult.Create(1, true);
yield return FakeSomeDataResult.Create(2, false);
}
如前一个答案中所提供的,这个方便的小包装器 class 允许实例化 ObjectResult:
public class TestableObjectResult<T> : ObjectResult<T> { }
如前一个答案中所提供的,在 [SetUp] 方法中:
var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
在这个新的 Mock 创建之后,将其设置为 return 枚举器:
mockObjectResult.Setup(d => d.GetEnumerator()).Returns(GetSomeDataResultEnumerator());
现在 OP 可以 return 来自 mockContext 的一些假数据,而不会在尝试获取枚举器时抛出空引用异常:
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
.Returns(mockObjectResult);
顺便说一句, 我只是使用助手 class 来构建我的假数据,以消除冗余:
public static class FakeSomeDataResult
{
public static usp_get_Some_Data_Result Create(int index)
{
return new usp_get_Some_Data_Result
{
SomeFriendlyNameProperty = string.Format("Some Data Result {0}", index),
};
}
}
如@forsvarir 所述,您可以创建一个 TestableObjectResult class 并将 GetEnumerator() 覆盖为 return 任何您想要的。
像这样:
private class TestableObjectResult : ObjectResult<Animal>
{
public override IEnumerator<Animal> GetEnumerator()
{
return new List<Animal>() { new Animal(), new Animal() }.GetEnumerator();
}
}
这是我的解决方案。
到目前为止它似乎对我有用。
public static class MoqExtentions
{
public static void SetupReturn<T>(this Mock<ObjectResult<T>> mock, T whatToReturn)
{
IEnumerator<T> enumerator = ((IEnumerable<T>) new T[] {whatToReturn}).GetEnumerator();
mock.Setup(or => or.GetEnumerator())
.Returns(() => enumerator);
}
}
现在您可以模拟 ObjectResult<T>
并调用将 ObjectResult<T>
设置为 return 的扩展方法,无论您传递给扩展方法什么。然后模拟的 ObjectResult<T>
可以作为方法调用的 return 值传递给模拟的 DbContext
方法设置。如果需要,可以将其传递给访问 ObjectResult<T>
.
这里是一个使用模拟ObjectResult<string>
public void MockObjectResultReturn_OfString_Test()
{
// arrange
const string shouldBe = "Hello World!";
var sut = new Mock<ObjectResult<string>>();
// act
sut.SetupReturn<string>(shouldBe);
//assert
Assert.IsNotNull(sut);
Assert.IsNotNull(sut.Object);
Assert.AreEqual(shouldBe, sut.Object?.FirstOrDefault());
}