我正在使用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的调用无法断言。这是为什么?
答案 0 :(得分:37)
It.IsAny
构造中使用, Setup
仅允许Moq匹配方法调用的未来调用。当调用Setup
时,Moq只是将方法调用添加到已经设置的方法调用的缓存中。请注意,示例中Setup
的参数的类型为Expression<Func<IFoo, bool>>
。由于传入Expression
,因此不会调用实际的方法调用,并且Moq能够遍历表达式以确定方法调用的哪些参数是显式的,哪些是It.IsAny
个参数。它使用此功能来确定运行时的未来方法调用是否与已设置的方法调用之一匹配。
为了使方法Bar
可以接受参数It.IsAny<int>()
,有必要让It.IsAny<int>()
返回int
(因为这是Bar
)的参数。通常,It.IsAny<T>
的返回类型必须为T
。必须选择T
的任意值。最自然的选择是default(T)
,它适用于引用类型和值类型。 (阅读有关默认关键字here)的更多信息。在您的情况下,即default(int)
,即0
。
因此,当您实际评估It.IsAny<int>()
时,会立即返回0
的值。但是,当您在It.IsAny<int>()
中使用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是开源的。来源可用here。我觉得拥有这些源代码很有价值,这样我就可以点击并探索代码和单元测试。
答案 1 :(得分:1)
It.IsAny<int>()
的返回类型为int并返回0
,因此您的第二个设置相当于:
mock.Setup(x => x.Bar2(0)).Returns(true);
我没有检查moq代码,但我很确定当它在setup方法中计算表达式时,它会考虑参数实际上是It.IsAny与正常数字。
如果您直接在助手方法中创建设置,并且不传递It.IsAny,那么您最好。