【问题标题】:Mocking Entity Framework 6 ObjectResult with Moq使用 Moq 模拟实体框架 6 ObjectResult
【发布时间】:2015-08-28 18:09:46
【问题描述】:

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

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

【问题讨论】:

    标签: c# entity-framework entity-framework-6 moq entity-framework-6.1


    【解决方案1】:

    首先,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 时,mock 对象结果需要返回一个枚举器:

    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 的人。

    【讨论】:

      【解决方案2】:

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

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

      例如

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

      示例


      这是一个简单的函数,它从 string 生成 IEnumerator&lt;string&gt;。这可以用匿名方法内联,但为了便于说明,这里更难阅读。

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

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

      var myObjResultReturnEnum = f( "some result" );
      

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

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

      不,我们有一个模拟的 ObjectResult&lt;string&gt;,我们可以将它传递给一个 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);
      

      【讨论】:

      • 不错。我得试试那种风格。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-08
      • 2019-07-07
      • 1970-01-01
      • 2011-04-18
      • 2014-08-02
      相关资源
      最近更新 更多