Skip to content

Commit ace97d3

Browse files
committed
Update documentation for matchers
1 parent 66991e4 commit ace97d3

File tree

3 files changed

+70
-0
lines changed

3 files changed

+70
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### 6.x (work in progress)
22

3+
* [NEW] `ArgMatchers.Matching` predicate matcher as an alternative to `Is(Expression<Predicate<T>>`. (.NET6 and above.)
4+
* [UPDATE] Improved support for custom argument matchers. `Arg.Is` now accepts arg matchers.
35
* [UPDATE][BREAKING] Update target frameworks: .NET8, .NET Standard 2.0
46
* [UPDATE] Drop EOL .NET 6/7 platforms from testing matrix
57
* [UPDATE] Update github actions steps versions

docs/help/argument-matchers/index.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ formatter.DidNotReceive().Format(Arg.Any<int>());
6767
```
6868

6969
## Conditionally matching an argument
70+
7071
An argument of type `T` can be conditionally matched using `Arg.Is<T>(Predicate<T> condition)`.
7172

7273
```csharp
@@ -94,6 +95,27 @@ Assert.That(formatter.Format("not matched, too long"), Is.Not.EqualTo("matched")
9495
Assert.That(formatter.Format(null), Is.Not.EqualTo("matched"));
9596
```
9697

98+
_[Since v6.0; .NET6 and above]_ An argument of type `T` can also be conditionally matched using `ArgMatchers.Matching`.
99+
100+
```csharp
101+
#if NET6_0_OR_GREATER
102+
103+
// With `using static NSubstitute.ArgMatchers`
104+
calculator.Add(1, -10);
105+
106+
//Received call with first arg 1 and second arg less than 0:
107+
calculator.Received().Add(1, Arg.Is(Matching<int>(x => x < 0)));
108+
//Received call with first arg 1 and second arg of -2, -5, or -10:
109+
calculator
110+
.Received()
111+
.Add(1, Arg.Is(Matching<int>(x => new[] {-2,-5,-10}.Contains(x))));
112+
//Did not receive call with first arg greater than 10:
113+
calculator.DidNotReceive().Add(Arg.Is(Matching<int>(x => x > 10)), -10);
114+
115+
#endif
116+
```
117+
118+
97119
## Matching a specific argument
98120
An argument of type `T` can be matched using `Arg.Is<T>(T value)`.
99121

@@ -127,6 +149,49 @@ Assert.That(memoryValue, Is.EqualTo(42));
127149

128150
See [Setting out and ref args](/help/setting-out-and-ref-arguments/) for more information on working with `out` and `ref`.
129151

152+
## Custom argument matchers
153+
154+
_[Since v6.0]_
155+
156+
Custom argument matching logic can be provided by implementing the `IArgumentMatcher<T>` interface in the `NSubstitute.Core.Arguments` namespace. Ideally custom matchers should also implement `NSubstitute.Core.IDescribeSpecification`, which explains what conditions an argument needs to meet to match the required condition, and `NSubstitute.Core.IDescribeNonMatches`, which provides an explanation about why a specific argument does not match.
157+
158+
Custom argument matchers can be used via `Arg.Is(IArgumentMatcher<T>)`.
159+
160+
For example:
161+
162+
```csharp
163+
class GreaterThanMatcher<T>(T value) :
164+
IDescribeNonMatches, IDescribeSpecification, IArgumentMatcher<T>
165+
where T : IComparable<T> {
166+
167+
public string DescribeFor(object argument) => $"{argument} ≯ {value}";
168+
public string DescribeSpecification() => $">{value}";
169+
public bool IsSatisfiedBy(T argument) => argument.CompareTo(value) > 0;
170+
}
171+
172+
public static IArgumentMatcher<T> GreaterThan<T>(T value) where T : IComparable<T> =>
173+
new GreaterThanMatcher<T>(value);
174+
175+
[Test]
176+
public void AddGreaterThan() {
177+
calculator.Add(1, 20);
178+
calculator.Received().Add(1, Arg.Is(GreaterThan(10)));
179+
}
180+
```
181+
182+
If the `GreaterThan` matcher fails, we get a message like:
183+
184+
```
185+
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
186+
Add(1, >10)
187+
Actually received no matching calls.
188+
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
189+
Add(1, *2*)
190+
arg[1]: 2 ≯ 10
191+
```
192+
193+
The `Add(1, >10)` part of the message uses `IDescribeSpecification`, while the `arg[1]: 2 ≯ 10` line is build from `IDescribeNonMatchers`.
194+
130195
## How NOT to use argument matchers
131196

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

tests/NSubstitute.Documentation.Tests.Generator/DocumentationTestsGenerator.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ private static string GenerateTestClassContent(string testsClassName, Additional
5555
$$"""
5656
using NUnit.Framework;
5757
using System.ComponentModel;
58+
using NSubstitute.Core;
59+
using NSubstitute.Core.Arguments;
5860
using NSubstitute.Extensions;
5961
using NSubstitute.ExceptionExtensions;
62+
using static NSubstitute.ArgMatchers;
6063
6164
namespace NSubstitute.Documentation.Tests.Generated;
6265

0 commit comments

Comments
 (0)