From bb6cb6a17e68d7931b144d6450bdcf11e8fc0dc9 Mon Sep 17 00:00:00 2001 From: Steven Weerdenburg Date: Fri, 20 Feb 2026 19:15:00 +0000 Subject: [PATCH] Add extension member examples for custom constraints --- .../extending-nunit/Custom-Constraints.md | 71 +++++++++++-------- .../Snippets.NUnit/CustomConstraints.cs | 45 ++++++++++++ .../Snippets.NUnit/Snippets.NUnit.csproj | 2 +- .../Snippets.NUnitLite.csproj | 2 +- 4 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 docs/snippets/Snippets.NUnit/CustomConstraints.cs diff --git a/docs/articles/nunit/extending-nunit/Custom-Constraints.md b/docs/articles/nunit/extending-nunit/Custom-Constraints.md index 63172c375..a1caf6f67 100644 --- a/docs/articles/nunit/extending-nunit/Custom-Constraints.md +++ b/docs/articles/nunit/extending-nunit/Custom-Constraints.md @@ -162,63 +162,72 @@ The direct construction approach is not very convenient or easy to read. For its classes that implement a special constraint syntax, allowing you to write things like... ```csharp -Assert.That(actual, Is.All.InRange(1, 100)); +Assert.That(actual, Is.Custom(x, y)); ``` -Custom constraints can support this syntax by providing a static helper class and extension method on -`ConstraintExpression`, such as this. +To fully utilize your custom constraint the same way built-in constraints are used, you'll need to implement two additional integration points (which can cover all your constraints, not for each custom constraint): + +* Provide a static method patterned after NUnit's `Is` class that can construct your custom + constraint. ```csharp -public static class CustomConstraintExtensions -{ - public static ContentsEqualConstraint ContentsEqual(this ConstraintExpression expression, object expected) - { - var constraint = new ContentsEqualConstraint(expected); - expression.Append(constraint); - return constraint; - } -} +Assert.That(actual, Is.Custom(x, y)); ``` -To fully utilize your custom constraint the same way built-in constraints are used, you'll need to implement two -additional classes (which can cover all your constraints, not for each custom constraint). - -* Provide a static class patterned after NUnit's `Is` class, with properties or methods that construct your custom - constructor. If you like, you can even call it `Is` and extend NUnit's `Is`, provided you place it in your own - namespace and avoid any conflicts. This allows you to write things like: +* Provide a method for NUnit's `ConstraintExpression` to allow chaining with other constraints, allowing you to write things like: ```csharp -Assert.That(actual, Is.Custom(x, y)); +Assert.That(actual, Is.Not.Custom(x, y)); ``` - with this sample implementation: +### Example 1 (Using C#14 Extension Members) + +Provide a static extension method on NUnit's `Is` class. ```csharp -public class Is : NUnit.Framework.Is +public static class CustomConstraintExtensions { - public static CustomConstraint Custom(object expected) + extension(Is) { - return new CustomConstraint(expected); + public static CustomConstraint Custom(string expected) + { + return new CustomConstraint(expected); + } } } ``` -* Provide an extension method for NUnit's `ConstraintExpression`, allowing you to write things like: +Provide an extension method for NUnit's `ConstraintExpression`. ```csharp -Assert.That(actual, Is.Not.Custom(x, y)); +public static class CustomConstraintExtensions +{ + extension(ConstraintExpression expression) + { + public CustomConstraint Custom(string expected) + { + var constraint = new CustomConstraint(expected); + expression.Append(constraint); + return constraint; + } + } +} ``` -with this sample implementation: +### Example 2 (Using Extension Methods and Inheritance) + +Extend NUnit's `Is` class and provide a static method. If you like, you can even call it `Is` and extend NUnit's `Is`, provided you place it in your own namespace and avoid any conflicts. ```csharp -public static class CustomConstraintExtensions +public class Is : NUnit.Framework.Is { - public static CustomConstraint Custom(this ConstraintExpression expression, object expected) + public static CustomConstraint Custom(object expected) { - var constraint = new CustomConstraint(expected); - expression.Append(constraint); - return constraint; + return new CustomConstraint(expected); } } ``` + +Provide an extension method for NUnit's `ConstraintExpression`. + +[!code-csharp[Syntax_Expression_ClassicExtension](~/snippets/Snippets.NUnit/CustomConstraints.cs#Syntax_Expression_ClassicExtension)] diff --git a/docs/snippets/Snippets.NUnit/CustomConstraints.cs b/docs/snippets/Snippets.NUnit/CustomConstraints.cs new file mode 100644 index 000000000..928fbade5 --- /dev/null +++ b/docs/snippets/Snippets.NUnit/CustomConstraints.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using NUnit.Framework.Constraints; +using static Snippets.NUnit.CustomConstraints; + +namespace Snippets.NUnit; + +public static class CustomConstraints +{ + #region SampleConstraint + public class CustomConstraint : Constraint + { + private readonly string _expected; + + public CustomConstraint(string expected) + { + _expected = expected; + } + + public override string Description => "Custom"; + + public override ConstraintResult ApplyTo(TActual actual) + { + if (actual is not string actualString) + { + return new ConstraintResult(this, actual, false); + } + + bool success = _expected == actualString; + return new ConstraintResult(this, actual, success); + } + } + #endregion +} + +#region Syntax_Expression_ClassicExtension +public static class CustomConstraintClassicExtensions +{ + public static CustomConstraint Custom(this ConstraintExpression expression, string expected) + { + var constraint = new CustomConstraint(expected); + expression.Append(constraint); + return constraint; + } +} +#endregion \ No newline at end of file diff --git a/docs/snippets/Snippets.NUnit/Snippets.NUnit.csproj b/docs/snippets/Snippets.NUnit/Snippets.NUnit.csproj index 0f4fedc74..44bf547f5 100644 --- a/docs/snippets/Snippets.NUnit/Snippets.NUnit.csproj +++ b/docs/snippets/Snippets.NUnit/Snippets.NUnit.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable NUnit2007;NUnit2009;CS7022 diff --git a/docs/snippets/Snippets.NUnitLite/Snippets.NUnitLite.csproj b/docs/snippets/Snippets.NUnitLite/Snippets.NUnitLite.csproj index e4d39b1ea..9f0da0de0 100644 --- a/docs/snippets/Snippets.NUnitLite/Snippets.NUnitLite.csproj +++ b/docs/snippets/Snippets.NUnitLite/Snippets.NUnitLite.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 Snippets.NUnitLite enable enable