Custom Assertions
The TUnit Assertions can be easily extended so that you can create your own assertions.
Creating a Custom Assertionâ
To create a custom assertion, you need to:
- Create an Assertion Class that inherits from
Assertion<TValue> - Implement the required methods
- Create an extension method on
IAssertionSource<T>
Step 1: Create an Assertion Classâ
Your assertion class should inherit from Assertion<TValue> where TValue is the type being asserted.
using TUnit.Assertions.Core;
public class StringContainsAssertion : Assertion<string>
{
private readonly string _expected;
private readonly StringComparison _comparison;
public StringContainsAssertion(
AssertionContext<string> context,
string expected,
StringComparison comparison = StringComparison.Ordinal)
: base(context)
{
_expected = expected ?? throw new ArgumentNullException(nameof(expected));
_comparison = comparison;
}
protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<string> metadata)
{
var value = metadata.Value;
var exception = metadata.Exception;
if (exception != null)
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
if (value == null)
return Task.FromResult(AssertionResult.Failed("value was null"));
if (value.Contains(_expected, _comparison))
return Task.FromResult(AssertionResult.Passed);
return Task.FromResult(AssertionResult.Failed($"'{value}' does not contain '{_expected}'"));
}
protected override string GetExpectation()
=> $"to contain \"{_expected}\"";
}
Step 2: Create an Extension Methodâ
Create an extension method on IAssertionSource<T> that returns your custom assertion:
using System.Runtime.CompilerServices;
using TUnit.Assertions.Core;
public static class StringAssertionExtensions
{
public static StringContainsAssertion ContainsIgnoreCase(
this IAssertionSource<string> source,
string expected,
[CallerArgumentExpression(nameof(expected))] string? expression = null)
{
source.Context.ExpressionBuilder.Append($".ContainsIgnoreCase({expression})");
return new StringContainsAssertion(
source.Context,
expected,
StringComparison.OrdinalIgnoreCase);
}
}
Step 3: Use Your Custom Assertionâ
await Assert.That("Hello World")
.ContainsIgnoreCase("WORLD"); // Uses your custom assertion!
Chaining with And/Orâ
Because your assertion returns an Assertion<T> type, it automatically supports chaining with .And and .Or:
await Assert.That("Hello World")
.ContainsIgnoreCase("WORLD")
.And
.ContainsIgnoreCase("HELLO");
Key Pointsâ
- Extension target: Always extend
IAssertionSource<T>so your method works on assertions, And, and Or continuations - Append expression: Call
source.Context.ExpressionBuilder.Append(...)to build helpful error messages - Return your assertion: Return a new instance of your custom
Assertion<T>subclass - Context sharing: Pass
source.Contextto your assertion constructor (it contains the evaluation context and expression builder) - CheckAsync parameter: Use
EvaluationMetadata<TValue> metadatawhich contains bothValueandExceptionproperties - CallerArgumentExpression: Use this attribute to capture parameter expressions for better error messages