diff --git a/docs/help/_posts/2010-04-01-argument-matchers.markdown b/docs/help/_posts/2010-04-01-argument-matchers.markdown index 68678373e..daf48bee2 100644 --- a/docs/help/_posts/2010-04-01-argument-matchers.markdown +++ b/docs/help/_posts/2010-04-01-argument-matchers.markdown @@ -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: @@ -156,10 +165,45 @@ subject.StartWithWidget(4); widgetFactory.Received().Make(Arg.Is(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 +var widgetFactory = Substitute.For(); + +// NOT OK: using an arg matcher as a value, not to specify a call: +// widgetFactory.Make(10).Returns(Arg.Any()); + +// 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(); +var subject = new Sprocket(widgetFactory); + +// OK: Use arg matcher to configure a callback: +var log = new List(); +widgetFactory.When(x => x.Make(Arg.Any())).Do(x => log.Add("Make called")); + +// OK: Use Arg.Do to configure a callback: +var log2 = new List(); +widgetFactory.Make(Arg.Do(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.