Search code examples
c#mockingnsubstitute

mock function to return different values each time


I'm using NSubstitute to mock my dependencies in my SuT. What I want to do is providing multiple return-values for a specific function. So each time the function is called, it should return a different value. As of the docs I can just add the values comma-seperated as follows:

var row = Substitute.For<ITable>();
table.NextRow().Returns(returnThis, returnThis2, returnThis3)

This function is called in a loop in my SuT:

while((table.NextRow() != null)
{ ... }

Now I'd expect that loop to run exactly as many times as I provide args to the mock - so in the above case exactly three times. When it's invoked a fourth time, I'd expect the mock to return the default-value for whatever the return-type of NextRow is - so null in case of a class. In my actual case I don't have exactly three variables but just an array of values my function should return:

var myArray = new[] { returnThis, returnThis2, returnThis3 };
table.NextRow().Returns(myArray[0], myArray.Skip(1).ToArray());

When I run my SuT, the loop never terminates and eventually I get an OutOfMemoryException. NextRow will never return null, but just keeps returning fake-instances for the ITable-interface. How can I "stop" providing values?


Solution

  • As you recognized the mock will keep returning fakes when you don't provide further ones. The fakes being returned are those of the last argument to Returns. So in the following code the variables b and c will be equal to two:

    [Test]
    public void T()
    {
        var m = Substitute.For<IMyInterface>();
        m.MyInt.Returns(1, 2);
        var a = m.MyInt;
        var b = m.MyInt;
        var c = m.MyInt;
    }
    

    So afterall you need to explictely add a null-element into your array:

    var rows = myArray.ToList();
    rows.Add(null);
    table.NextRow.Returns(rows[0], rows.Skip(1).ToArray());
    

    From a pure .Net-centric view it may sound a bit strange that the default-value of a fake isn't the default-value of that type. However it makes sense, when you look at how NSubsitite is used in a fluent way:

    var m = Substitute.For<IMyInterface>();
    m.MyMember.AnotherMember.Returns(whatever);
    

    When NSubsitite chosed to return null for reference-types, you'd need to add a further Returns-call to m.MyMember as well which will make our test-code pretty cluttered:

    var m = Substitute.For<IMyInterface>();
    var a = Substitute.For<IAnotherInterface>();
    a.AnotherMember.Returns(whatever);
    m.MyMember.Returns(a);
    

    However NSubsitite will just give you a default implementation for that members type, when you don't explictely provide one.