I am fairly new to unit testing in C# and learning to use Moq. Below is the example of my problem with Moq Verify()
method.
[Theory]
[AutoData]
public async Task WhenSomething_ThenSomething(IEnumerable<string> stringCollection)
{
//Arange
//Mock of the method used in Handle() that returns stringCollection.
someMock.Setup(x => x.MethodReturningStringCollectionForHandler()).ReturnsAsync(stringCollection);
var someList = stringCollection.ToList();
//Act
await handler.Handle(new SomeRequest(someId));
//Assert
//I want to verify if someMethod() inside handler was called once but with appropriate stringCollection
problematicMock.Verify(x => x.someMethod(someList), Times.Once());
}
The above scenerio works, but when I remove someList
variable and use ToList()
directly in Verify()
, like this:
problematicMock.Verify(x => x.someMethod(stringCollection.ToList()), Times.Once());
then I get the following exception:
Message:
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.someMethod(["83436e1f-bd2f-44d3-9f8c-ba6afbf73e95", "16593c11-0959-4ebe-aafd-d5fbe0cfbd17", "633e6557-bed0-4ff0-b550-8790fab9e629"])
As one could imagine, this is quite problematic, what if someMethod()
would accept many collection-type parameters? For this particular example I would have to create many variables to pass to Verify()
method. Why does it work this way?
In short Moq compares List<T>
instances on reference basis. The two ToList
calls create two separate collections therefor their references are different.
In order to overcome of this you need to use It.Is
inside your Verify
problematicMock.Verify(
x => x.someMethod(
It.Is<List<string>>(input => AssertCollection(stringCollection.ToList(), input))),
Times.Once());
It.Is
receives a Func<List<string>, bool>
delegateinput
is the argument of the someMethod
callHere is a naive implementation of the AssertCollection
:
public static bool AssertCollection(List<string> expected, List<string> actual)
{
try
{
Assert.Equal(expected, actual);
}
catch
{
return false;
}
return true;
}
If you pass stringCollection.ToList()
as an expected value then it will pass, but if you pass
stringCollection.Reverse.ToList()
or stringCollection.Skip(1).ToList()
then it will fail.