【问题标题】:Why can't I create a callback for the List Find method in Moq?为什么我不能在 Moq 中为 List Find 方法创建回调?
【发布时间】:2016-09-12 21:12:58
【问题描述】:

我创建了一个扩展方法,让我可以将 List 视为 DbSet 以进行测试(实际上,我在另一个关于堆栈溢出的问题中发现了这个想法,它非常有用)。编码如下:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var queryable = sourceList.AsQueryable();

        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockDbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
        mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback(sourceList.Find);
        return mockDbSet.Object;
    }

我使用 Add 已经有一段时间了,效果很好。但是,当我尝试为 Find 添加回调时,我收到一个编译器错误,指出它无法将方法组转换为操作。为什么sourceList.Add是一个Action,而sourceList.Find是一个方法组?

我承认我对 C# 委托并不是特别熟悉,所以我很可能遗漏了一些非常明显的东西。提前致谢。

【问题讨论】:

    标签: entity-framework unit-testing delegates entity-framework-6 moq


    【解决方案1】:

    Add 起作用的原因是因为List&lt;T&gt;.Add 方法组包含一个方法,该方法采用T 类型的单个参数并返回 void。此方法与Action&lt;T&gt; 具有相同的签名,这是Callback 方法的重载之一(具有单个泛型类型参数Callback&lt;T&gt;),因此List&lt;T&gt;.Add 方法组可以转换为Action&lt;T&gt;.

    使用Find,您正在尝试调用Callback 方法(与Callback&lt;T&gt; 相对),该方法需要Action 参数(相对于Action&lt;T&gt;)。这里的区别在于Action 不带任何参数,而Action&lt;T&gt; 只带一个类型为T 的参数。List&lt;T&gt;.Find 方法组不能转换为Action,因为所有Find 方法(反正只有一个)接受输入参数。

    以下将编译:

        public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
        {
            var mockDbSet = new Mock<DbSet<T>>();
            mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<Predicate<T>>(t => sourceList.Find(t));
            return mockDbSet.Object;
        }
    

    请注意,我之所以调用 .Callback&lt;Predicate&lt;T&gt;&gt;,是因为 List&lt;T&gt;.Find 方法需要 Predicate 类型的参数。另请注意,我不得不写t =&gt; sourceList.Find(t) 而不是sourceList.Find,因为Find 返回一个值(这意味着它与Action&lt;Predicate&lt;T&gt;&gt; 的签名不匹配)。通过将其编写为 lambda 表达式,返回值将被丢弃。

    请注意,尽管这样编译它实际上不会起作用,因为 DbSet.Find 方法实际上将 object[] 作为其参数,而不是 Predicate&lt;T&gt;,因此您可能必须执行以下操作:

        public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
        {
            var mockDbSet = new Mock<DbSet<T>>();
            mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<object[]>(keyValues => sourceList.Find(keyValues.Contains));
            return mockDbSet.Object;
        }
    

    最后一点更多地与如何使用 Moq 库有关,以及如何使用方法组、委托和 lambdas - 这一行包含各种语法糖,隐藏了与编译器实际相关的内容什么不是。

    【讨论】:

    • 谢谢!几个小时以来,我一直在反对这一点,您的解释非常有用。
    • 没问题。我应该在回答的后半部分补充一点,我变得懒惰,不再区分方法和方法组。如果你再次陷入这个主题,我强烈推荐 Jon Skeet 的“C# in depth”一书。它更准确地解释了方法组、lambda 和委托之间的转换过程是如何工作的。
    • 所以当我在 mock 上调用 Find 时,为了真正从 Callback 方法中获取值,我想我必须做这样的事情:mockDbSet.Setup(d =&gt; d.Find(It.IsAny&lt;object[]&gt;())).Callback&lt;object[]&gt;(k =&gt; item = sourceList.Find(k.Contains)).Returns(() =&gt; item); 接下来我会看看这本书我有机会的时候。
    • 几乎 - 使用Returns 代替 Callback(不要将两者链接在一起),您将获得价值。例如mockDbSet.Setup(d =&gt; d.Find(It.IsAny&lt;object[]&gt;())).Returns&lt;object[]&gt;(keyValues =&gt; sourceList.Find(keyValues.Contains));
    猜你喜欢
    • 2013-10-05
    • 2018-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 1970-01-01
    相关资源
    最近更新 更多