【问题标题】:What is the difference between passing It.IsAny<int>() and the value of It.IsAny<int>() to a method setup将 It.IsAny<int>() 和 It.IsAny<int>() 的值传递给方法设置有什么区别
【发布时间】:2026-02-17 14:50:02
【问题描述】:

我正在使用 Moq,并希望创建构建器类来创建具有预设合理默认值的模拟,这些默认值可以根据需要在测试设置期间被覆盖。我采用的方法使用扩展方法,在这些方法中我传递输入参数值和预期输出。这样做时,我看到在我看来是语义等效的代码中有不同的行为:直接在设置中传递 It.IsAny() 与在设置中间接传递 It.IsAny() 的值。示例:

public interface IFoo
{
    bool Bar(int value);
    bool Bar2(int value);
}
public class Foo : IFoo
{
    public bool Bar(int value) { return false; }
    public bool Bar2(int value) { return false; }
}

var mock = new Mock<IFoo>();
mock.Setup(x => x.Bar(It.IsAny<int>())).Returns(true);
Assert.IsTrue(mock.Object.Bar(123));                    // Succeeds

var myValue = It.IsAny<int>();
mock.Setup(x => x.Bar2(myValue)).Returns(true);
Assert.IsTrue(mock.Object.Bar2(123));                   // Fails

这两个调用都是等价的(对我来说),但是对 Bar2 的调用失败了断言。这是为什么呢?

【问题讨论】:

    标签: c# moq


    【解决方案1】:

    It.IsAny 如果在 Setup 构造中使用,则仅允许 Moq 匹配未来的方法调用调用。当Setup 被调用时,Moq 只需将方法调用添加到其已设置方法调用的缓存中。请注意,示例中Setup 的参数类型为Expression&lt;Func&lt;IFoo, bool&gt;&gt;。由于您传入的是 Expression,因此不会调用实际的方法调用,并且 Moq 能够遍历表达式以确定方法调用的哪些参数是显式的,哪些是 It.IsAny 参数。它使用此功能来确定运行时未来的方法调用是否与已设置的方法调用之一匹配。

    为了使方法Bar可以接受参数It.IsAny&lt;int&gt;(),有必要让It.IsAny&lt;int&gt;()返回一个int(因为这是Bar的参数类型)。一般来说It.IsAny&lt;T&gt;的返回类型必须是T。必须选择T 的任意值。最自然的选择是default(T),它适用于引用类型和值类型。 (阅读有关默认关键字 here 的更多信息)。在您的情况下,即default(int),即0

    因此,当您实际评估 It.IsAny&lt;int&gt;() 时,会立即返回 0 的值。但是,当您在 Expression 中使用 It.IsAny&lt;int&gt;() 时(如在 Setup 方法的参数中),方法调用的树结构将被保留,并且 Moq 可以将未来的方法调用与由Expression

    因此,尽管您不能以任何有意义的方式将 It.IsAny&lt;int&gt;() 保留为变量,但您可以将整个 Expression 保留在变量中:

    Expression<Func<IFoo, bool>> myExpr = x => x.Bar2(It.IsAny<int>());
    mock.Setup(myExpr).Returns(true);
    Assert.IsTrue(mock.Object.Bar2(123));  
    

    最后,我只想提醒您,Moq 是开源的。来源可用here。我发现拥有该源代码很有价值,这样我就可以四处点击并探索代码和单元测试。

    【讨论】:

    • 很好的解释,本。正如您所描述的,我怀疑问题在于表达式评估,而不是 It.IsAny() 的返回值。我查看了 ILSpy 中的反射源,可以看到 It.IsAny,但无法理解它是如何解决问题的。再次感谢您的解释。
    • 感谢您为我解开巫术!我想说“var anyFooParam = It.IsAny(); var anyBarParam = It.IsAny();”看看它是否会使我的测试更具可读性,例如: .Setup(mock => mock.method(anyFooParam, anyBarParam)).Returns(something!)
    【解决方案2】:

    It.IsAny&lt;int&gt;() 的返回类型为 int 并返回 0,因此您的第二个设置相当于:

    mock.Setup(x => x.Bar2(0)).Returns(true);
    

    我没有检查起订量代码,但我很确定当它评估 setup 方法中的表达式时,它考虑到参数实际上是 It.IsAny 与正常数字。

    最好直接在辅助方法中创建设置,而不是传递 It.IsAny。

    【讨论】: