29

I'm using Moq and want to create builder classes to create my mocks with preset reasonable defaults that can be overridden during test setup as needed. The approach I took uses extension methods in which I pass input parameter values and expected output. In doing so, I'm seeing different behavior in what seems to me to be semantically equivalent code: passing It.IsAny() directly in a setup vs passing the value of It.IsAny() indirectly in a setup. Example:

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

Both calls are equivalent (to me), yet the call to Bar2 fails assertion. Why is this?

4

2 回答 2

43

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

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

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

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

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

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

于 2013-06-12T03:04:06.323 回答
1

It.IsAny<int>() has return type of int and returns 0, so your second setup is equivalent to:

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

I didn't check the moq code, but I'm pretty sure that when it evaluates the expression in the setup method, it takes into account that the parameter is actually It.IsAny vs. a normal number.

You are better off if you create the setups directly in your helper methods, and not pass It.IsAny.

于 2013-06-11T21:06:12.947 回答