使用Moq模拟实体框架6 ObjectResult

问题描述 投票:2回答:2

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

按照这些方式阅读了许多问题和答案,并从我所读过的内容中收集了许多金块,我已经实现了我认为是一个相当优雅的解决方案,并且觉得我应该分享它,因为这里的社区帮助我实现了目标。因此,我将继续回答这个问题,并可能打开一些嘲弄(双关语):

c# entity-framework entity-framework-6 moq entity-framework-6.1
2个回答
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)测试覆盖率。

希望这有助于那些像我一样,正在寻找一种干净的方式来模拟Entity Framework 6。


1
投票

我多次遇到这个问题

我的解决方案是模拟ObjectResult的GetEnumeartor方法。这可以很容易地在一个测试方法内完成,只需很少的开销

for example

假设我们有一个repo方法,它调用返回ObjectResult<string>的DBContext方法。 repo方法通过枚举stringObjectResult<string>得到结果值objResult.FirstOrDefault()。如你所知,LINQ方法只是调用ObjectResult的enumeartor。因此,嘲笑GetEnumeartor()ObjectResult功能将起到作用。任何enumeartion of teh结果都将返回我们的模拟枚举器。

Example


这是一个简单的简单函数,从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);
© www.soinside.com 2019 - 2024. All rights reserved.