简体   繁体   English

用Moq模拟实体框架6 ObjectResult

[英]Mocking Entity Framework 6 ObjectResult with Moq

How can I mock the Entity Framework 6 ObjectResult with Moq so that I can unit test my code that relies on an EF database connection? 如何使用Moq模拟Entity Framework 6 ObjectResult,以便可以对依赖EF数据库连接的代码进行单元测试?

Having read numerous questions and answers along these lines, and gleaned many nuggets from what I've read, I've implemented what I feel is a reasonably elegant solution and felt that I should share it, since the community here helped me get there. 沿着这些思路阅读了无数的问题和答案,并从我所阅读的内容中收集了很多知识,我实现了我认为是一个相当优雅的解决方案,并且我应该分享它,因为这里的社区帮助我实现了这一目标。 Thus, I'll proceed to answer this question, and potentially open myself up to some mockery (pun intended): 因此,我将继续回答这个问题,并可能使自己对某些嘲笑(双关语意):

First of all, ObjectResult does not have a public parameterless constructor, thus it is necessary first to create a testable wrapper for ObjectResult. 首先,ObjectResult没有公共的无参数构造函数,因此有必要首先为ObjectResult创建一个可测试的包装器。 The answer by @forsvarir ( https://stackoverflow.com/users/592182/forsvarir ) in this post got me thinking correctly along these lines ( EF6 - Cannot Mock Return Value for ObjectResult<T> for Unit Test ): @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> { }
}

Of course, the DbContext needs to be mocked. 当然,需要模拟DbContext。 Your method then needs to be set up to return the appropriate mocked enumerator. 然后需要设置您的方法以返回适当的模拟枚举器。 For convenience, I created a method to help in the creation of the Mock EF Results to keep my test code from getting cluttered and redundant. 为了方便起见,我创建了一个方法来帮助创建模拟EF结果,以防止测试代码变得混乱和多余。 This can live in some utilitarian class that you have for your tests, though I just included it as a private method here. 这可以存在于您要进行测试的一些实用程序类中,尽管我只是在此处将其作为私有方法包括在内。 The key thing here being that the mock object result needs to return an enumerator when GetEnumerator is called: 这里的关键是,调用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;
        }
    }
}

The Enumerators class that I created for handing back the enumerator whenever the function is called on the mock simply looks like this. 我创建的用于枚举该枚举数的Enumerators类,就像在模拟中调用该函数一样。 In this example I have the fake enumerator creating 5 rows of data: 在此示例中,我使用了伪造的枚举器来创建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);
        }
    }
}

And, as you can see, this simply relies on a class that creates each fake row of data: 而且,正如您所看到的,这仅依赖于一个创建每个伪数据行的类:

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

Being able to mock at this level really enables my ability to do BDD and only mock or fake the peripheries, never mocking or faking my code , so I get complete(r) test coverage. 能够在此级别进行模拟实际上使我能够进行BDD,并且仅模拟或伪造外围设备,而从不模拟或伪造我的代码 ,因此可以完成测试的范围。

Hope this helps those who, like me, were looking for a nice clean way to mock Entity Framework 6. 希望这对像我一样正在寻找一种不错的简洁方法来模拟Entity Framework 6的人有所帮助。

I have run into this problem many times 我已经多次遇到这个问题

My solution is to mock a ObjectResult's GetEnumeartor method. 我的解决方案是模拟ObjectResult的GetEnumeartor方法。 This can easily be done within a test method will very little overhead 这可以很容易地在一种测试方法中完成,将花费很少的开销

for example 例如

say we have a repo method that calls a DBContext method that returns a ObjectResult<string> . 说我们有一个repo方法,该方法调用一个DBContext方法,该方法返回一个ObjectResult<string> The repo method would get the resulting value string by enumearting the ObjectResult<string> with maybe objResult.FirstOrDefault() . repo方法将通过枚举ObjectResult<string>可能是objResult.FirstOrDefault()来获得结果值string As you know the LINQ method just calls into the ObjectResult 's enumeartor. 如您所知,LINQ方法只是调用ObjectResult的enumeartor。 Therefore, mocking the GetEnumeartor() function of the ObjectResult will do the trick. 因此, ObjectResultGetEnumeartor()函数可以解决问题。 Any enumeartion of teh result will return our mocked enumerator. 对结果的任何枚举都将返回模拟的枚举器。

Example


Here is a simple a simple func that produces an IEnumerator<string> from a string . 下面是产生一个简单的简单FUNC IEnumerator<string>从一个string This can be inlined with an anonymous method but it makes it harder to read for illustration purposes here. 可以使用匿名方法内联该方法,但是在这里出于说明目的使其难以阅读。

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

this func can be called by passing a string like this 可以通过传递这样的字符串来调用该函数

var myObjResultReturnEnum = f( "some result" );

this may make it easier for us to get the IEnumerator<string> we expect the ObjectResult<string> 's GetEnumerator() method to return so any repo method that calls the ObjectResult will receive our string 这可能使我们更容易获取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 );

No we have a mocked ObjectResult<string> that we can pass into a repo method inferring our method will eventually call into the enumerator in some way and will get our shouldBe value. 不,我们有一个模拟的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