2

I have come across is situation when mocking a method with an output parameter using NSubstitute. I'm not sure how best to explain it in text, so I'll use some contrived examples and test cases...

In this contrived example, I'll be using a NSubstitute mock of a IDictionary<string, string>.

private static IDictionary<string, string> GetSubstituteDictionary()
{
    IDictionary<string, string> dict = Substitute.For<IDictionary<string, string>>();

    string s;
    dict.TryGetValue("key", out s).Returns(ci => { ci[1] = "value"; return true; });

    return dict;
}

Now, when I use this mock object in a simple manner, it returns as expected:

[Test]
public void ExampleOne()
{
    var dict = GetSubstituteDictionary();

    string value;
    bool result = dict.TryGetValue("key", out value);

    Assert.That(result, Is.True); // this assert passes.
    Assert.That(value, Is.EqualTo("value")); // this assert passes.
}

However, when I call the same code in a for loop, I get some unexpected behaviour:

[Test]
public void ExampleTwo()
{
    var dict = GetSubstituteDictionary();

    for (int i = 0; i < 2; i++)
    {
        string value;
        bool result = dict.TryGetValue("key", out value);

        Assert.That(result, Is.True); // this assert FAILS - unexpected!
        Assert.That(value, Is.EqualTo("value")); // this assert still passes.
    }
}

In particular, the Assert.That(result, Is.True); assertion passes on the first iteration of the loop, but fails on the second (and any subsequent) iteration.

However, if I modify the string value; line to be string value = null;, the assertion passes for all iterations.

What is causing this anomaly? Is this due to some semantics of C# for loops that I am missing, or is it an issue with the NSubstitute library?

Matthew King
  • 5,114
  • 4
  • 36
  • 50

2 Answers2

6

The reason is that the value variable changes in the loop (set via the output parameter), so that it no longer matches the call you stubbed out.

You can try using .ReturnsForAnyArgs(), although you'll need to check the key within the stub then rather than via an argument matcher.

David Tchepak
  • 9,826
  • 2
  • 56
  • 68
  • Thanks, that makes sense when `value` is declared outside the loop; Is that the case even when `value` is declared within the loop body, and is thus in scope only within the loop body? I would have thought each iteration of the loop would have its own "`value`". – Matthew King Jul 26 '12 at 04:08
  • 1
    I believe the compiler recognises it can reuse the `value` variable. My IL reading skills aren't good enough to say for certain, but I ran it through the debugger and `value` is still "value" on the next loop iteration. If you change the line to `string value = null;` the test passes fine. – David Tchepak Jul 26 '12 at 07:39
0

As @DavidTchepak already said, the problem comes from the fact that the value of the value local variable is null on the first run but becomes value on the following. Though using .ReturnsForAnyArgs() will work, it adds complexity in the substitue Returns(delegate) function.

To keep it as close as possible to the OP version, you could simply make sure to init the value variable to null on each loop like so

[TestMethod]
public void ExampleTwo()
{
    var dict = GetSubstituteDictionary();

    for (int i = 0; i < 2; i++)
    {
        string value = null;
        bool result = dict.TryGetValue("key", out value);

        result.Should().BeTrue(); // this assert FAILS - unexpected!
        value.Should().Be("value"); // this assert still passes.
    }
}

or, if you want to to be more semantically strong and NSubstitue version is at least 4.0, you could use Arg.Any<string>() as it is explained in the NSubstitute documentation and presented below.

private static IDictionary<string, string> GetSubstituteDictionary()
{
    IDictionary<string, string> dict = Substitute.For<IDictionary<string, string>>();
    _ = dict.TryGetValue("key", out Arg.Any<string>()).Returns(ci => { ci[1] = "value"; return true; });

    return dict;
}
bkqc
  • 831
  • 6
  • 25