Argument matchers

Argument matchers can be used when setting return values and when checking received calls. They provide a way to specify a call or group of calls, so that a return value can be set for all matching calls, or to check a matching call has been received.

The argument matchers syntax shown here depends on having C# 7.0 or later. If you are stuck on an earlier version (getting an error such as CS7085: By-reference return type 'ref T' is not supported while trying to use them) please use compatibility argument matchers instead.

⚠️ Note: Argument matchers should only be used when setting return values or checking received calls. Using Arg.Is or Arg.Any without a call to Returns(...) or Received() can cause your tests to behave in unexpected ways. See How NOT to use argument matchers for more information.

Ignoring arguments

An argument of type T can be ignored using Arg.Any<T>().

calculator.Add(Arg.Any<int>(), 5).Returns(7);

Assert.AreEqual(7, calculator.Add(42, 5));
Assert.AreEqual(7, calculator.Add(123, 5));
Assert.AreNotEqual(7, calculator.Add(1, 7));

In this example we return 7 when adding any number to 5. We use Arg.Any<int>() to tell NSubstitute to ignore the first argument.

We can also use this to match any argument of a specific sub-type.

formatter.Format(new object());
formatter.Format("some string");

formatter.Received().Format(Arg.Any<object>());
formatter.Received().Format(Arg.Any<string>());
formatter.DidNotReceive().Format(Arg.Any<int>());

Conditionally matching an argument

An argument of type T can be conditionally matched using Arg.Is<T>(Predicate<T> condition).

calculator.Add(1, -10);

//Received call with first arg 1 and second arg less than 0:
calculator.Received().Add(1, Arg.Is<int>(x => x < 0));
//Received call with first arg 1 and second arg of -2, -5, or -10:
calculator
    .Received()
    .Add(1, Arg.Is<int>(x => new[] {-2,-5,-10}.Contains(x)));
//Did not receive call with first arg greater than 10:
calculator.DidNotReceive().Add(Arg.Is<int>(x => x > 10), -10);

If the condition throws an exception for an argument, then it will be assumed that the argument does not match. The exception itself will be swallowed.

formatter.Format(Arg.Is<string>(x => x.Length <= 10)).Returns("matched");

Assert.AreEqual("matched", formatter.Format("short"));
Assert.AreNotEqual("matched", formatter.Format("not matched, too long"));
// Will not match because trying to access .Length on null will throw an exception when testing
// our condition. NSubstitute will assume it does not match and swallow the exception.
Assert.AreNotEqual("matched", formatter.Format(null));

Matching a specific argument

An argument of type T can be matched using Arg.Is<T>(T value).

calculator.Add(0, 42);

//This won't work; NSubstitute isn't sure which arg the matcher applies to:
//calculator.Received().Add(0, Arg.Any<int>());

calculator.Received().Add(Arg.Is(0), Arg.Any<int>());

This matcher normally isn’t required; most of the time we can just use 0 instead of Arg.Is(0). In some cases though, NSubstitute can’t work out which matcher applies to which argument (arg matchers are actually fuzzily matched; not passed directly to the function call). In these cases it will throw an AmbiguousArgumentsException and ask you to specify one or more additional argument matchers. In some cases you may have to explicitly use argument matchers for every argument.

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);

See Setting out and ref args for more information on working with out and ref.

How NOT to use argument matchers

Occasionally argument matchers get used in ways that cause unexpected results for people. Here are the most common ones.

Using matchers outside of stubbing or checking received calls

Argument matchers should only be used when setting return values or checking received calls. Using Arg.Is or Arg.Any without a call to Returns(...) or Received() can cause your tests to behave in unexpected ways.

For example:

/* ARRANGE */

var widgetFactory = Substitute.For<IWidgetFactory>();
var subject = new Sprocket(widgetFactory);

// OK: Use arg matcher for a return value:
widgetFactory.Make(Arg.Is<int>(x => x > 10)).Returns(TestWidget);

/* ACT */

// NOT OK: arg matcher used with a real call:
//   subject.StartWithWidget(Arg.Any<int>());

// Use a real argument instead:
subject.StartWithWidget(4);

/* ASSERT */

// OK: Use arg matcher to check a call was received:
widgetFactory.Received().Make(Arg.Is<int>(x => x > 0));

In this example it would be an error to use an argument matcher in the ACT part of this test. Even if we don’t mind what specific argument we pass to our subject, Arg.Any is only for substitutes, and only for setting return values or checking received calls; not for real calls.

(If you do want to indicate to readers that the precise argument used for a real call doesn’t matter you could use a variable such as var someInt = 4; subject.StartWithWidget(someInt); or similar. Just stay clear of argument matchers for this!)

Modifying values being matched

When NSubstitute records calls, it keeps a reference to the arguments passed, not a deep clone of each argument at the time of the call. This means that if the properties of an argument change after the call assertions may not behave as expected.

public class Person {
    public string Name { get; set; }
}

[Test]
public void MutatingAMatchedArgument() {
    var person = new Person { Name = "Carrot" };
    var lookup = Substitute.For<IPersonLookup>();

    // Called with a Person that has a .Name property of "Carrot"
    lookup.Add(person);

    // The Name in that person reference later gets updated ...
    person.Name = "Vimes";

    // When the substitute is queried, it will check the fields of the person reference it was called with.
    // This means the argument it was called with does NOT have a .Name of "Carrot" (it was changed!)
    lookup.DidNotReceive().Add(Arg.Is<Person>(p => p.Name == "Carrot"));
    // Instead, it now has the updated name:
    lookup.Received().Add(Arg.Is<Person>(p => p.Name == "Vimes"));
}

This looks confusing at first, but if we remember substitutes are pretty much forced to store references to arguments used then it makes sense. The alternative of storing deep-cloned snapshots of every argument to every call received is fairly impractical, especially if we consider objects with very complex hierarchies (e.g. tens of fields, each with an object with tens of fields of its own, etc.). Storing snapshots would also lead to the same confusion in the reverse situation, where we know a substitute was called with a particular reference but the Arg.Is(person) check fails due to a change in one of its fields.

That said, there are times when snapshots like this are useful, and there are a few ways to enable this with NSubstitute.

The first option is to use structs instead of classes for these cases. These are passed by value rather than by reference, so that value will be stored by substitutes and modifications made afterwards will not affect that value.

public struct PersonStruct {
    public string Name { get; set; }
}

[Test]
public void MutatingAStruct() {
    var person = new PersonStruct { Name = "Carrot" };
    var lookup = Substitute.For<IPersonStructLookup>();

    lookup.Add(person);

    person.Name = "Vimes";

    // `person` was passed by value, and that value still has the original Name
    lookup.Received().Add(Arg.Is<PersonStruct>(p => p.Name == "Carrot"));
}

For cases where that is not possible or wanted then we can manually snapshot the values we are interested in.

[Test]
public void ManualArgSnapshot() {
    var person = new Person { Name = "Carrot" };
    var lookup = Substitute.For<IPersonLookup>();
    var namesAdded = new List<string>();
    // Manually snapshot the value or values we care about:
    lookup.Add(Arg.Do<Person>(p => namesAdded.Add(p.Name)));


    lookup.Add(person);
    person.Name = "Vimes";

    Assert.AreEqual("Carrot", namesAdded[0]);
}

We can then use our standard assertion library for checking the value. This approach can also be helpful for asserting on complex objects, as our assertions can be more detailed and provide more useful information than NSubstitute typically provides in these cases.