-1

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?

igy234
  • 59
  • 1
  • 7
  • You're setting up `someMethod` with the parameter `someId`, but you're verifying it was called with `someList`. Is that correct? Please post a [mre]. – gunr2171 Mar 01 '22 at 14:53
  • Corrected the example, sorry for the confusion. – igy234 Mar 01 '22 at 14:57
  • You've made edits, but those edits make your code example less of a [mre]. I don't know now how `problematicMock` is setup, or what `handler.Handle` is. Please make the code that you post self-contained. – gunr2171 Mar 01 '22 at 14:59
  • 1
    someList and a later stringCollection.ToList() are to different lists.They may contain exactly the same data but they are not the same List instance. – Ralf Mar 01 '22 at 14:59
  • @Ralf so how to verify just the data inside the collection and why it works for `someList` and not for `stringCollection.ToList()` even though the data are the same? – igy234 Mar 01 '22 at 15:10
  • 1
    I assume Moq internally uses the IEquatable interface for comparing when its implemented. List does not. So the thing Moq presumably does here is comparing the references (the memory address where the data is) and they aren't the same reference so Moq will say you did not call it with the same reference and barks. It might not even know that a list is involved. It just knows that it is an object that isn't comparable and the references are different. – Ralf Mar 01 '22 at 15:14
  • For readibilty setup and verify of the mock should be in the same method. So using the same list in both occurrences seems no problem to me. – Ralf Mar 01 '22 at 15:15

1 Answers1

1

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75