Skip to content

Commit

Permalink
Update arg matcher misuse docs
Browse files Browse the repository at this point in the history
Update after discussion at issue 35[^1].

[^1]: nsubstitute/NSubstitute.Analyzers#35 (comment)
  • Loading branch information
dtchepak committed Apr 23, 2019
1 parent ae57666 commit 9aac5a3
Showing 1 changed file with 44 additions and 2 deletions.
46 changes: 44 additions & 2 deletions docs/help/_posts/2010-04-01-argument-matchers.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,16 @@ Occasionally argument matchers get used in ways that cause unexpected results fo

### 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.
Argument matchers should only be used when specifying calls for the purposes of setting return values, checking received calls, or configuring callbacks (for example: with `Returns`, `Received` or `When`). Using `Arg.Is` or `Arg.Any` in other situations can cause your tests to behave in unexpected ways.

Argument matchers should only be used for:

* Specifying a call when using `Returns` and `ReturnsForAnyArgs`
* Specifying a call within a `When` or `WhenForAnyArgs` block to configure a callback/call action
* Specifying a call to check with `Received`, `DidNotReceive` and `Received.InOrder`
* Configuring a callback with `Arg.Do` or `Arg.Invoke`

Using an argument matcher without one of these calls is most likely an error.

For example:

Expand All @@ -156,10 +165,43 @@ subject.StartWithWidget(4);
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.
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 specifying a call while setting return values, checking received calls or for configuring callbacks; 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!)

Similarly, we should not use an arg matcher as a real value to return from a call (even a substituted one).

```csharp
// NOT OK: using an arg matcher as a value, not to specify a call:
// widgetFactory.Make(10).Returns(Arg.Any<string>());
// Instead use something like:
widgetFactory.Make(10).Returns("any string");
```

Another legal use of argument matchers is specifying calls when configuring callbacks:

```csharp
/* ARRANGE */
var widgetFactory = Substitute.For<IWidgetFactory>();
var subject = new Sprocket(widgetFactory);

// OK: Use arg matcher to configure a callback:
var log = new List<string>();
widgetFactory.When(x => x.Make(Arg.Any<int>())).Do(x => log.Add("Make called");

// OK: Use Arg.Do to configure a callback:
var log2 = new List<string>();
widgetFactory.Make(Arg.Do<int>(id => log2.Add($"Make({id}) called"));

/* ACT */
subject.StartWithWidget(42);

/* ASSERT */
Assert.AreEqual(new[] { "Make called" }, log);
Assert.AreEqual(new[] { "Make(42) called", log2);
```

### 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.
Expand Down

0 comments on commit 9aac5a3

Please sign in to comment.