Search code examples
c#unit-testingmoqxunitautofixture

Moq Verify method not working with ToList


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?


Solution

  • 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> delegate
    • input is the argument of the someMethod call

    Here 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.