What is the difference between passing It.IsAny<int>() and the value of It.IsAny<int>() to a method setup

微笑、不失礼 提交于 2019-11-26 11:28:36

问题


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?


回答1:


It.IsAny only allows Moq to match future invocations of method calls if used within the Setup construct. When Setup is called Moq just adds the method call to its cache of already-set-up method calls. Note that the argument to Setup in your example has type Expression<Func<IFoo, bool>>. Since you are passing in an Expression, the actual method call is not invoked and Moq has the ability to traverse the expression to figure out which parameters of the method call were explicit, and which are It.IsAny arguments. It uses this ability to determine if a future method call at runtime matches one of the already-set-up method calls.

In order to make it so that the method Bar can accept argument It.IsAny<int>(), it is necessary to make It.IsAny<int>() return an int (since that is the type of the parameter of Bar). In general, the return type of It.IsAny<T> must be T. An arbitrary value of T must be chosen. The most natural choice is default(T), which works for reference types and value types. (Read more about the default keyword here). In your case, that is default(int), which is 0.

So when you actually evaluate It.IsAny<int>() the value of 0 is immediately returned. However, when you use It.IsAny<int>() in an Expression (as in the argument to the Setup method), then the tree structure of the method call is preserved and Moq can match future method invocations to the method call encapsulated by the Expression.

So, although you cannot keep It.IsAny<int>() as a variable in any meaningful way, you can keep the entire Expression in a variable:

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

Finally, I just want to remind you that Moq is open source. The source is available here. I find it valuable to have that source code so that I can click around and explore the code and the unit tests.




回答2:


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.



来源:https://stackoverflow.com/questions/17053064/what-is-the-difference-between-passing-it-isanyint-and-the-value-of-it-isany

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!