C# - NSubstitute, how to mock and match a call that takes a list of items as a parameter

NSubstitute is a great framework for mocking objects for tests. I recently had an unexpected behaviour when stubbing a method call. For this simple example we will use the following interface:

public interface ISummationService
{
    public int SumNumbers(List<int> numbers);
}

You can imagine the above being the interface for a class that can sum a list of numbers. The below is a simple stub of this, where the numbers 1, 2, 3 and 4 are summed and the result should be 10:

var myServiceSub = Substitute.For<ISummationService>();
var numbers = new List<int> { 1, 2, 3, 4 };
myServiceSub.SumNumbers(numbers).Returns(10);
var result = myServiceSub.SumNumbers(numbers);
Assert.Equal(10, result); // is true!

In the above we use the same list for the argument matching myServiceSub.SumNumbers(numbers).Returns(10); as for the method call in the next line myServiceSub.SumNumbers(numbers);. This means that it is the same object in both places and since it is the same reference it is equal. If we were to introduce a new list for the call of the method:

var myServiceSub = Substitute.For<ISummationService>();
var numbers = new List<int> { 1, 2, 3, 4 };
var aDifferentNumbersList = new List<int> { 1, 2, 3, 4 };
myServiceSub.SumNumbers(numbers).Returns(10);
var result = myServiceSub.SumNumbers(aDifferentNumbersList);
Assert.Equal(10, result); // is false!

Even though the lists in the above have the same values, they are two different objects. Therefore the argument matching fails as they are not equal. This is a gotcha that can take you back to your computer science classes! So the above assertions fail as NSubstitute ends up returning a default value - and for integers that is zero.

The solution

To get around this, we can use the Arg.Is argument matcher together with SequenceEqual:

var myServiceSub = Substitute.For<ISummationService>();
var numbers = new List<int> { 1, 2, 3, 4 };
var aDifferentNumbersList = new List<int> { 1, 2, 3, 4 };
myServiceSub.SumNumbers(Arg.Is<List<int>>(x => x.SequenceEqual(numbers))).Returns(10);
var result = myServiceSub.SumNumbers(aDifferentNumbersList);
Assert.Equal(10, result); // is true!

This way we can match the argument by iterating through the lists and comparing them. With Arg.Is you can fully control whether something should be considered matched or not.

That is all

I hope you found the above helpful, if you have a comment please leave it down below!