【问题标题】:EF6 - Cannot Mock Return Value for ObjectResult<T> for Unit TestEF6 - 无法模拟单元测试的 ObjectResult<T> 的返回值
【发布时间】:2015-09-13 18:49:14
【问题描述】:

我在尝试单元测试的方法中有类似的代码:

return _context.usp_get_Some_Data(someStringParam).FirstOrDefault();

存储过程调用返回类型:

ObjectResult<usp_get_Some_Data_Result>. 

在我的单元测试中,我正在尝试做这样的事情(使用 NUnit 和 Moq):

var procResult = new ObjectResult<usp_get_Some_Data_Result>();
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
    .Returns(procResult);

但是,我无法创建 ObjectResult 的实例(这是 System.Data.Entity.Core.Objects.ObjectResult,而不是旧的 System.Data.Objects 实例)。它没有公共的无参数构造函数,但documentation 说它有一个受保护的构造函数。根据我的测试,他的文档似乎不正确。

我的尝试: 我尝试在构造函数上创建派生类并调用 base(),并且我还尝试使用反射(Activator.CreateInstance 和使用 NonPublic 的 BindingFlags 调用 ConstructorInfo,所有这些都失败了(从我的调试中出现)这表明该类型确实具有三个私有构造函数,所有这些构造函数都有 3 个或更多参数,但不幸的是,要弄清楚这些参数实际需要什么似乎是一项重大努力。

我也尝试创建一个 IEnumberable 并将其转换为 ObjectResult 但转换失败。另外,我尝试过类似

var mockObjectResult = new Mock<ObjectResult<usp_get_Some_Data_Result>>();

我尝试过的几乎所有事情都失败了,并出现了关于默认构造函数不可用的类似错误。

问题: 有没有办法为单元测试创​​建 ObjectResult 的实例,或者我可以创建可以成功转换为 ObjectResult 的任何其他类型?

【问题讨论】:

    标签: c# unit-testing mocking entity-framework-6 moq


    【解决方案1】:

    也许我错过了什么,但你不能这样做:

    class TestableObjectResult<T> : ObjectResult<T>
    {
    }
    

    然后在你的测试中:

    var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
    

    MockObject 确实有一个受保护的构造函数,你不需要做任何事情来调用它,因为它没有任何参数,当你构造可测试版本时,自动连接会处理它,所以我'我不确定你的意思是“在构造函数上调用 base()”......

    如果我右键单击 ObjectResult 并选择 goto 定义,文件顶部如下所示:

    public class ObjectResult<T> : ObjectResult, IEnumerable<T>, IEnumerable, IDbAsyncEnumerable<T>, IDbAsyncEnumerable
    {
        // Summary:
        //     This constructor is intended only for use when creating test doubles that
        //     will override members with mocked or faked behavior. Use of this constructor
        //     for other purposes may result in unexpected behavior including but not limited
        //     to throwing System.NullReferenceException.
        protected ObjectResult();
    

    【讨论】:

    • 上述继承自 ObjectResult 的类声明编译失败:“错误 1 ​​The type 'System.Data.Entity.Core.Objects.ObjectResult' has no constructors defined” I如果我将构造函数添加到 TestableObjectResult 类,则会得到相同的错误。
    • @dgavian 您使用的是哪个版本的 EF?我刚刚从 nuget 重新获取它,它编译得很好。
    • @dgavian 我已将源代码的顶部添加到我的答案中以证明它应该在那里.. 右键单击​​它并转到定义。如果您与众不同,那么您可能没有使用您认为自己的版本......
    • 这很奇怪。我不认为从 6.1.0 更新到 6.1.3 会有这么大的不同,但它现在可以编译了。谢谢!
    • @crazyTech 如上面的问题/cmets 所述,这是指来自实体框架版本 6 的 ObjectResult。github.com/aspnet/EntityFramework6/blob/master/src/…
    【解决方案2】:

    如前所述,我将添加此答案以涵盖创建 Enumerator 以便上面可以实际测试一些假数据:

    在 [TestFixture] 类中,创建如下方法:

    private static IEnumerator<usp_get_Some_Data_Result> GetSomeDataResultEnumerator()
    {
        yield return FakeSomeDataResult.Create(1, true);
        yield return FakeSomeDataResult.Create(2, false);
    }
    

    正如上一个答案中所提供的,这个方便的小包装类允许实例化 ObjectResult:

    public class TestableObjectResult<T> : ObjectResult<T> { }
    

    如上一个答案中提供的,在 [SetUp] 方法中:

    var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();
    

    在这个新的 Mock 创建之后,将其设置为返回 Enumerator:

    mockObjectResult.Setup(d => d.GetEnumerator()).Returns(GetSomeDataResultEnumerator());
    

    现在 OP 可以从 mockContext 返回一些假数据,而不会在尝试获取枚举器时抛出空引用异常:

    mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
        .Returns(mockObjectResult);
    

    顺便说一句, 我只是使用辅助类来构造我的假数据,以消除冗余:

    public static class FakeSomeDataResult
    {
        public static usp_get_Some_Data_Result Create(int index)
        {
            return new usp_get_Some_Data_Result
            {
                SomeFriendlyNameProperty = string.Format("Some Data Result {0}", index),
            };
        } 
    }
    

    【讨论】:

      【解决方案3】:

      正如@forsvarir 提到的,您可以创建一个 TestableObjectResult 类并覆盖 GetEnumerator() 以返回您想要的任何内容。

      类似这样的:

      private class TestableObjectResult : ObjectResult<Animal>
          {
              public override IEnumerator<Animal> GetEnumerator()
              {
                  return new List<Animal>() { new Animal(), new Animal() }.GetEnumerator();
              }
          }
      

      【讨论】:

        【解决方案4】:

        这是我的解决方案。

        到目前为止,它似乎对我有用。

        public static class MoqExtentions
        {
            public static void SetupReturn<T>(this Mock<ObjectResult<T>> mock, T whatToReturn)
            {
                IEnumerator<T> enumerator = ((IEnumerable<T>) new T[] {whatToReturn}).GetEnumerator();
        
                mock.Setup(or => or.GetEnumerator())
                    .Returns(() => enumerator);
            }
        }
        

        现在您可以模拟ObjectResult&lt;T&gt; 并调用设置ObjectResult&lt;T&gt; 的扩展方法以返回您传递给扩展方法的任何内容。然后可以将模拟的ObjectResult&lt;T&gt; 传递给模拟的DbContext 方法设置作为方法调用的返回值。如果需要,可以将其传递给访问 ObjectResult&lt;T&gt; 的模拟 repo 方法。

        这是一个使用模拟的ObjectResult&lt;string&gt;的测试示例

            public void MockObjectResultReturn_OfString_Test()
            {
                // arrange
                const string shouldBe = "Hello World!";
                var sut = new Mock<ObjectResult<string>>();
        
                // act
                sut.SetupReturn<string>(shouldBe);
        
                //assert
                Assert.IsNotNull(sut);
                Assert.IsNotNull(sut.Object);
                Assert.AreEqual(shouldBe, sut.Object?.FirstOrDefault());
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-14
          • 2021-07-01
          相关资源
          最近更新 更多