使用 Moq 模拟 Entity Framework 6 个 ObjectResult

Mocking Entity Framework 6 ObjectResult with Moq

如何使用 Moq 模拟 Entity Framework 6 ObjectResult,以便对依赖于 EF 数据库连接的代码进行单元测试?

按照这些思路阅读了大量的问题和答案,并从我所阅读的内容中收集了许多金块,我实施了我认为相当优雅的解决方案,并认为我应该分享它,因为这里的社区帮助我到达那里。因此,我将继续回答这个问题,并可能让自己受到一些嘲弄(双关语意):

首先,ObjectResult 没有public 无参数构造函数,因此首先需要为ObjectResult 创建一个可测试的包装器。 @forsvarir (https://whosebug.com/users/592182/forsvarir) in this post got me thinking correctly along these lines () 的回答:

using System.Data.Entity.Core.Objects;

namespace MyNamespace.Mocks
{
    public class TestableEfObjectResult<T> : ObjectResult<T> { }
}

当然,DbContext 需要被模拟。然后需要将您的方法设置为 return 适当的模拟枚举器。为方便起见,我创建了一种方法来帮助创建模拟 EF 结果,以防止我的测试代码变得混乱和冗余。这可以存在于您用于测试的一些实用 class 中,尽管我只是将其作为私有方法包含在这里。这里的关键是当调用 GetEnumerator 时,模拟对象结果需要 return 一个枚举器:

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;
        }
    }
}

我创建的枚举器 class 用于在模拟上调用该函数时交还枚举器,就像这样。在此示例中,我使用假枚举器创建了 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);
        }
    }
}

而且,如您所见,这仅依赖于创建每个假数据行的 class:

namespace MyNamespace.FakeData
{
    public static class FakeSomeResult
    {
        public static Some_Result Create(int id)
        {
            return new Some_Result
            {
                Id = id,
            };
        }
    }
}

能够在这个级别进行模拟确实使我能够进行 BDD,并且只模拟或伪造外围设备,从不模拟或伪造 我的代码,所以我完成了(r ) 测试覆盖率。

希望这可以帮助那些像我一样正在寻找一种干净整洁的方法来模拟的人 Entity Framework6.

这个问题我运行多次

我的解决方案是模拟 ObjectResult 的 GetEnumeartor 方法。这可以在测试方法中轻松完成,开销很小

例如

假设我们有一个调用 DBContext 方法的 repo 方法,return是一个 ObjectResult<string>。 repo 方法将通过枚举 ObjectResult<string>objResult.FirstOrDefault() 来获得结果值 string。如您所知,LINQ 方法只是调用 ObjectResult 的枚举器。因此,模拟 ObjectResultGetEnumeartor() 函数就可以了。任何枚举结果都将 return 我们模拟的枚举器。

例子


这是一个从 string 生成 IEnumerator<string> 的简单函数。这可以使用匿名方法进行内联,但为了便于说明,此处更难阅读。

var f = new Func< string,IEnumerator< string> >( s => ( ( IEnumerable< string > )new []{ s } ).GetEnumerator( ) );

可以通过传递这样的字符串来调用此函数

var myObjResultReturnEnum = f( "some result" );

这可能使我们更容易获得 IEnumerator<string> 我们期望 ObjectResult<string>GetEnumerator() 方法 return 所以任何回购调用 ObjectResult 的方法将接收我们的字符串

// arrange
const string shouldBe = "Hello World!";
var objectResultMock = new Mock<ObjectResult<string>>();
objectResultMock.Setup( or => or.GetEnumerator() ).Returns(() => myObjResultReturnEnum );

不,我们有一个模拟的 ObjectResult<string>,我们可以将其传递给 repo 方法,推断我们的方法最终将以某种方式调用枚举器并获得我们的 shouldBe 值.

var contextMock = new Mock<SampleContext>( );
contextMock.Setup( c => c.MockCall( ) ).Returns( ( ) => objectResultMock.Object );

// act
var repo = new SampleRepository( contextMock.Object );
var result = repo.SampleSomeCall( );

// assert
Assert.IsNotNull(result);
Assert.AreEqual(shouldBe, result);