Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use It.IsAny as a parameter?

Tags:

c#

moq

I have a test class invoking a mock of MyClass, I've Setup both DoStuffA and DoStuffB previously.

I've tried to wrap several Verify calls within a method, like so:

void VerifyMany(int input)
{
    _myClassMock.Verify(ic => ic.DoStuffA(input), Times.Once());
    _myClassMock.Verify(ic => ic.DoStuffB(input), Times.Once());
}

If I call my method with It.IsAny<int>() as the input - VerifyMany(It.IsAny<int>()) - my tests don't pass, it will however work if I call the Verify method directly with It.IsAny:

_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());
_myClassMock.Verify(ic => ic.DoStuffB(It.IsAny<int>()), Times.Once());

I understand from the answer to this question that Moq handles It.IsAny differently within an expression when specified to Setup/Verify, is there any workaround for this?

like image 297
JohnoBoy Avatar asked Feb 14 '19 08:02

JohnoBoy


2 Answers

The reason this doesn't work:

void VerifyMany(int input)
{
    _myClassMock.Verify(ic => ic.DoStuffA(input), Times.Once());
    _myClassMock.Verify(ic => ic.DoStuffB(input), Times.Once());
}

Is because It.IsAny<int>() (when simply invoked) returns 0. So essentially when VerifyMany is called with It.IsAny<int>(), you're passing 0 to VerifyMany. Which in turn means DoStuffA(0) and DoStuffB(0) are verified (not Any integer value as you probably intended it).

The other invocation:

_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());
_myClassMock.Verify(ic => ic.DoStuffB(It.IsAny<int>()), Times.Once());

Does work because the ic => ic.DoStuffA(It.IsAny<int>() part is never directly invoked. It is turned into an Expression Tree and Moq only walks (or visits if you like) that Expression Tree. Which means it will find It.IsAny<int>() in the Expression Tree and is then able to verify a call to DoStuffA (which it also found in the same expression tree) with any integer value. (for more on expression trees, feel free to read this)

You could make this semi-work by creating a method which abstracts the call to Verify away and accepts an expression like so:

void VerifyOnce(Expression<Action<ClassMockIsBasedOn>> callToVerify)
{
    _myClassMock.Verify(callToVerify, Times.Once());
}

Which allows you to call that like so:

VerifyOnce(ic => ic.DoStuffA(It.IsAny<int>())

You could also extend the VerifyOnce example to accept multiple expressions. That would allow you to verify DoStuffA and DoStuffB on a single line:

void VerifyOnce(params Expression<Action<ClassMockIsBasedOn>>[] callsToVerify)
{
    foreach(var callToVerify in callsToVerify) 
    {
        _myClassMock.Verify(callToVerify, Times.Once());
    }
}

That would allow a call like so:

VerifyOnce(ic => ic.DoStuffA(It.IsAny<int>(),
           ic => ic.DoStuffB(It.IsAny<int>());

And of course you could replace ClassMockIsBasedOn with a generic. And add an overload which allows for methods which return values (instead of being void) or accept more than one parameter as Brett suggested in the comments.

like image 125
Vincent Avatar answered Oct 11 '22 13:10

Vincent


It.IsAny only allows Moq to match future invocations of method calls if used within the Setup construct, you can't do it with Verify or Assert if your Setup isn't set It.IsAny yet. Because It.IsAny always return a default value of input.

So if you want to use It.IsAny at Verify you should Setup for target method first:

Correct

_myClassMock.Setup(ic => ic.DoStuffA(It.IsAny<int>())).Returns(// Something here);
_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());

Wrong

_myClassMock.Setup(ic => ic.DoStuffA(1)).Returns(// Something here);
_myClassMock.Verify(ic => ic.DoStuffA(It.IsAny<int>()), Times.Once());
like image 45
Nguyen Thanh Avatar answered Oct 11 '22 13:10

Nguyen Thanh