Skip to content

Commit f37985f

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

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-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: 61 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,23 @@ 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+
// With `using static NSubstitute.ArgMatchers`
102+
calculator.Add(1, -10);
103+
104+
//Received call with first arg 1 and second arg less than 0:
105+
calculator.Received().Add(1, Arg.Is(Matching<int>(x => x < 0)));
106+
//Received call with first arg 1 and second arg of -2, -5, or -10:
107+
calculator
108+
.Received()
109+
.Add(1, Arg.Is(Matching<int>(x => new[] {-2,-5,-10}.Contains(x))));
110+
//Did not receive call with first arg greater than 10:
111+
calculator.DidNotReceive().Add(Arg.Is(Matching<int>(x => x > 10)), -10);
112+
```
113+
114+
97115
## Matching a specific argument
98116
An argument of type `T` can be matched using `Arg.Is<T>(T value)`.
99117

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

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

148+
## Custom argument matchers
149+
150+
_[Since v6.0]_
151+
152+
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.
153+
154+
Custom argument matchers can be used via `Arg.Is(IArgumentMatcher<T>)`.
155+
156+
For example:
157+
158+
```csharp
159+
class GreaterThanMatcher<T>(T value) :
160+
IDescribeNonMatches, IDescribeSpecification, IArgumentMatcher<T>
161+
where T : IComparable<T> {
162+
163+
public string DescribeFor(object argument) => $"{argument} ≯ {value}";
164+
public string DescribeSpecification() => $">{value}";
165+
public bool IsSatisfiedBy(T argument) => argument.CompareTo(value) > 0;
166+
}
167+
168+
public static IArgumentMatcher<T> GreaterThan<T>(T value) where T : IComparable<T> =>
169+
new GreaterThanMatcher<T>(value);
170+
171+
[Test]
172+
public void AddGreaterThan() {
173+
calculator.Add(1, 20);
174+
calculator.Received().Add(1, Arg.Is(GreaterThan(10)));
175+
}
176+
```
177+
178+
If the `GreaterThan` matcher fails, we get a message like:
179+
180+
```
181+
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
182+
Add(1, >10)
183+
Actually received no matching calls.
184+
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
185+
Add(1, *2*)
186+
arg[1]: 2 ≯ 10
187+
```
188+
189+
The `Add(1, >10)` part of the message uses `IDescribeSpecification`, while the `arg[1]: 2 ≯ 10` line is build from `IDescribeNonMatchers`.
190+
130191
## How NOT to use argument matchers
131192

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