如何使用Moq模拟Entity Framework 6 ObjectResult,以便我可以对依赖于EF数据库连接的代码进行单元测试?
从这些方面阅读了许多问题和答案,并从我所读过的内容中收集了许多金块,我已经实现了我认为是一个相当优雅的解决方案,并认为我应该分享它,因为这里的社区帮助了我到达那里。因此,我将继续回答这个问题,并可能打开自己的一些嘲弄(双关语):
答案 0 :(得分:5)
首先,ObjectResult没有公共无参数构造函数,因此首先需要为ObjectResult创建一个可测试的包装器。 @forsvarir(https://stackoverflow.com/users/592182/forsvarir)在这篇文章中给出的答案让我正确地思考了这些问题(EF6 - Cannot Mock Return Value for ObjectResult<T> for Unit Test):
using System.Data.Entity.Core.Objects;
namespace MyNamespace.Mocks
{
public class TestableEfObjectResult<T> : ObjectResult<T> { }
}
当然,需要嘲笑DbContext。然后需要设置您的方法以返回适当的模拟枚举器。为方便起见,我创建了一种方法来帮助创建模拟EF结果,以防止我的测试代码变得混乱和冗余。这可以存在于你的测试中的一些实用类中,尽管我在这里只是将它作为私有方法包含在内。这里的关键是模拟对象结果需要在调用GetEnumerator时返回一个枚举器:
namespace MyNamespace.Mocks
{
public class MockSomeDbEntities
{
public static Mock<SomeDbEntities> Default
{
get
{
var mockSomeDbEntities = new Mock<SomeDbEntities>();
mockSomeDbEntities
.Setup(e => e.SomeMethod(It.IsAny<int>()))
.Returns(MockEfResult(Enumerators.SomeCollection).Object);
return mockSomeDbEntities;
}
}
private static Mock<TestableEfObjectResult<T>> MockEfResult<T>(Func<IEnumerator<T>> enumerator) where T : class
{
var mock = new Mock<TestableEfObjectResult<T>>();
mock.Setup(m => m.GetEnumerator()).Returns(enumerator);
return mock;
}
}
}
我创建的Enumerators类,只要在模拟上调用函数,就会回复枚举器,就像这样。在这个例子中,我有假枚举器创建5行数据:
using System;
using System.Collections.Generic;
namespace MyNamespace.FakeData
{
public static class Enumerators
{
public static IEnumerator<Some_Result> SomeCollection()
{
yield return FakeSomeResult.Create(1);
yield return FakeSomeResult.Create(2);
yield return FakeSomeResult.Create(3);
yield return FakeSomeResult.Create(4);
yield return FakeSomeResult.Create(5);
}
}
}
而且,正如您所看到的,这只是依赖于创建每个伪造数据行的类:
namespace MyNamespace.FakeData
{
public static class FakeSomeResult
{
public static Some_Result Create(int id)
{
return new Some_Result
{
Id = id,
};
}
}
}
能够在这个级别进行模拟真的能够让我能够做BDD,只能模仿或伪造外围,从不嘲笑或伪造我的代码,所以我得到完整的(r)测试覆盖率。 / p>
希望这有助于那些像我一样,正在寻找一种干净的方式来模拟实体框架6的人。
答案 1 :(得分:1)
我的解决方案是模拟ObjectResult的GetEnumeartor方法。这可以很容易地在一个测试方法内完成,只需很少的开销
例如 说我们有一个repo方法,它调用一个返回ObjectResult<string>
的DBContext方法。 repo方法会通过枚举string
并使用ObjectResult<string>
来获得结果值objResult.FirstOrDefault()
。如您所知,LINQ方法只需调用ObjectResult
的enumeartor。因此,模仿GetEnumeartor()
的{{1}}函数就可以了。任何结果都会返回我们的模拟枚举器。
这是一个简单的简单函数,它从ObjectResult
生成IEnumerator<string>
。这可以使用匿名方法进行内联,但是为了便于阅读,这使得阅读更加困难。
string
可以通过传递像
这样的字符串来调用此函数var f = new Func< string,IEnumerator< string> >( s => ( ( IEnumerable< string > )new []{ s } ).GetEnumerator( ) );
这可能会让我们更容易获得var myObjResultReturnEnum = f( "some result" );
我们期望IEnumerator<string>
的{{1}}方法返回所以任何调用ObjectResult的repo方法将收到我们的字符串
ObjectResult<string>
不,我们有一个模拟的GetEnumerator()
我们可以传递给一个repo方法,推断我们的方法最终将以某种方式调用枚举器并获得// arrange
const string shouldBe = "Hello World!";
var objectResultMock = new Mock<ObjectResult<string>>();
objectResultMock.Setup( or => or.GetEnumerator() ).Returns(() => myObjResultReturnEnum );
值。
ObjectResult<string>