繁体   English   中英

用Moq模拟实体框架6 ObjectResult

[英]Mocking Entity Framework 6 ObjectResult with Moq

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

沿着这些思路阅读了无数的问题和答案,并从我所阅读的内容中收集了很多知识,我实现了我认为是一个相当优雅的解决方案,并且我应该分享它,因为这里的社区帮助我实现了这一目标。 因此,我将继续回答这个问题,并可能使自己对某些嘲笑(双关语意):

首先,ObjectResult没有公共的无参数构造函数,因此有必要首先为ObjectResult创建一个可测试的包装器。 @forsvarir( https://stackoverflow.com/users/592182/forsvarir )的回答使我沿着这些思路正确思考( EF6-无法模拟单元测试的ObjectResult <T>的返回值 ):

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,并且仅模拟或伪造外围设备,而从不模拟或伪造我的代码 ,因此可以完成测试的范围。

希望这对像我一样正在寻找一种不错的简洁方法来模拟Entity Framework 6的人有所帮助。

我已经多次遇到这个问题

我的解决方案是模拟ObjectResult的GetEnumeartor方法。 这可以很容易地在一种测试方法中完成,将花费很少的开销

例如

说我们有一个repo方法,该方法调用一个DBContext方法,该方法返回一个ObjectResult<string> repo方法将通过枚举ObjectResult<string>可能是objResult.FirstOrDefault()来获得结果值string 如您所知,LINQ方法只是调用ObjectResult的enumeartor。 因此, ObjectResultGetEnumeartor()函数可以解决问题。 对结果的任何枚举都将返回模拟的枚举器。


下面是产生一个简单的简单FUNC IEnumerator<string>从一个string 可以使用匿名方法内联该方法,但是在这里出于说明目的使其难以阅读。

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

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

var myObjResultReturnEnum = f( "some result" );

这可能使我们更容易获取IEnumerator<string>我们希望ObjectResult<string>GetEnumerator()方法返回,因此任何调用ObjectResult的repo方法都将收到我们的字符串

// 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM