模拟实体框架6对象结果与Moq

本文关键字:结果 Moq 对象 实体 框架 模拟 | 更新日期: 2023-09-27 18:10:12

我如何用Moq模拟实体框架6 objectreresult,以便我可以对依赖EF数据库连接的代码进行单元测试?

在阅读了大量的问题和答案之后,从我所读到的内容中收集了许多宝贵的信息,我已经实现了我认为相当优雅的解决方案,并且觉得我应该分享它,因为这里的社区帮助我实现了这一点。因此,我将继续回答这个问题,并可能使自己受到一些嘲弄(双关语):

模拟实体框架6对象结果与Moq

首先,ObjectResult没有公共无参数构造函数,因此必须首先为ObjectResult创建一个可测试的包装器。@forsvarir (https://stackoverflow.com/users/592182/forsvarir)在这篇文章中的回答让我正确地思考了这些问题(EF6 -不能模拟objectresult的返回值对于单元测试):

using System.Data.Entity.Core.Objects;
namespace MyNamespace.Mocks
{
    public class TestableEfObjectResult<T> : ObjectResult<T> { }
}
当然,DbContext需要被模拟。然后需要将方法设置为返回适当的模拟枚举数。为了方便起见,我创建了一个方法来帮助创建Mock 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类是这样的:每当在mock上调用该函数时,该类将返回枚举数。在本例中,我使用假枚举器创建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)测试覆盖率。

希望这有助于那些谁,像我一样,正在寻找一个很好的干净的方式来模拟实体框架6。

这个问题我已经遇到过很多次了

我的解决方案是模拟objectreresult的getenmeartor方法。这可以很容易地在一个测试方法中完成,而且开销很小

例如

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

例子

这是一个简单的函数,从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()方法返回,所以任何调用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);