15

I'm trying to mock IConfigurationProvider with NSubstitute. I need the method bool TryGet(string key, out string value) to return values for differing keys. So something like this:

var configProvider = Substitute.For<IConfigurationProvider>();
configProvider.TryGet("key1", out Arg.Any<string>()).Returns(x => 
    { x[1] = "42"; return true; });

but this does not compile. I need the mocked method to actually set the out parameter to the appropriate value, regardless of what that parameter is - it's a dependency, the unit under test calls this method with its own parameters and I just want it to "return" (as in return by filling the out parameter) correct values for keys.

This should give more perspective on the problem:

var value = "";
var configProvider = Substitute.For<IConfigurationProvider>();
configProvider
.TryGet("key1", out value)
.Returns(x => { 
    x[1] = "42"; 
    return true; 
});

var otherValue = "other";
configProvider.TryGet("key1", out value);
configProvider.TryGet("key1", out otherValue);

Assert.AreEqual("42", value);      // PASS.
Assert.AreEqual("42", otherValue); // FAIL.

I need both assertions to be true, since this method will be used by the tested class and it's free to pass any out parameter it wants, I just need to fill it with "42".

Nkosi
  • 235,767
  • 35
  • 427
  • 472
V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • `configProvider.TryGet("key1", out Arg.Any())` is not valid c# syntax You need to use an actual variable for the *out* parameter. – Nkosi Jun 30 '18 at 18:17

2 Answers2

23

configProvider.TryGet("key1", out Arg.Any<string>()) is not valid C# syntax, which is why it wont compile.

You need to use an actual variable for the out parameter.

The following works when tested.

//Arrange            
var expectedResult = true;
var expectedOut = "42";
var actualOut = "other";
var anyStringArg = Arg.Any<string>();
var key = "key1";
var configProvider = Substitute.For<IConfigurationProvider>();
configProvider
    .TryGet(key, out anyStringArg)
    .Returns(x => {
        x[1] = expectedOut;
        return expectedResult;
    });

//Act
var actualResult = configProvider.TryGet(key, out actualOut);

//Assert
Assert.AreEqual(expectedOut, actualOut); // PASS.
Assert.AreEqual(expectedResult, actualResult); // PASS.
Nkosi
  • 235,767
  • 35
  • 427
  • 472
5

As of NSubstitute 4+ this is supported out the box:

Matching out and ref args


Argument matchers can also be used with out and ref (NSubstitute 4.0 and later with C# 7.0 and later).

calculator
    .LoadMemory(1, out Arg.Any<int>())
    .Returns(x => {
        x[1] = 42;
        return true;
    });

var hasEntry = calculator.LoadMemory(1, out var memoryValue);
Assert.AreEqual(true, hasEntry); Assert.AreEqual(42, memoryValue); 

Source

Make sure you note the argument index used above (x[1] = 42;), this array includes the input and output variables but you can only set the value of an out variable.

Liam
  • 27,717
  • 28
  • 128
  • 190
  • 2
    It seems that a later update to the compiler may have eliminated that usage option, at least in the case where the returned value is an object of some sort. I'm getting a failure to compile with a CS1510 error in Visual Studio 2019 v16.9.6. – Richard J Foster Jun 17 '21 at 16:04