NUnit supports several ways to assert against data. The recommended one, constraint-based, allows for a more naturally-read syntax which supports the flexible chaining of conditions to prove simple or complex facets about the system.
It follows the “Assert that” format:
Assert.That(actualValue, Is.EqualTo(expectedValue));
With the following components (listed left to right):
actualValue: The actual output of the system under test which you wish to validateIs: A starting clause. The most common isIsbut NUnit also definesHas,Does, and others to allow for readable testsEqualTo(): A example function which returns the contraint which validates your data.EqualToreturns an instance of anEqualConstraintclass which will internally contains validation logic. Other examples includeLessThan()orEven.expectedValue: The data to compareactualValueto using the rules defined by the constraint.
NUnit also contains built-in operators. For example, checking inequality is a matter to prepending the Not operator in front of EqualTo():
Assert.That(actualValue, Is.Not.EqualTo(expectedValue));
Similarly, if a situation requires check a characteristic of a value instead of comparing then a unary constraint like Even could be used:
Assert.That(actualValue, Is.Even);
The built-in constraints will likely meet 99.9% of use cases, but there may be some domain-specific rules which aren’t covered out-of-the-box. For example, a math-oriented program may wish to validate that a number is prime. It would be very nice if a test could be written to read:
Assert.That(actualValue, Is.Prime);
NUnit supports this through custom constraints. Custom constraints are classes which extend NUnit’s own Constraint class.
A PrimeConstraint may look like:
public class PrimeConstraint : Constraint
{
public override string Description => "A prime value";
public override ConstraintResult ApplyTo<TActual>(TActual actualValue)
{
var actualInt = Convert.ToInt32(actualValue);
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(actualInt, 0, nameof(actualInt));
for (int i = 2; i <= (int)Math.Sqrt(actualInt); i++)
{
if (actualInt % i == 0)
{
// Not prime if we've found an evenly divisible factor
return new ConstraintResult(this, actualValue, false);
}
}
return new ConstraintResult(this, actualValue, true);
}
}
In addition to extending from the Constraint class, the class must implement an ApplyTo<TActual>() method which will validate the actualValue originally passed into Assert.That(). Hooking this into the NUnit syntax tree is then very easy thanks to the new C# 14 extension members feature. Adding a new function onto NUnit’s static Is class and adding a new property onto the ConstraintExpression class can be achieved in one line each.
public static class ConstraintExtensions
{
extension(NUnit.Framework.Is)
{
public static Constraint Prime => new PrimeConstraint();
}
extension(ConstraintExpression expression)
{
public Constraint Prime => expression.Append(new PrimeConstraint());
}
}
And that’s it. They can be used in tests seamlessly afterwards as if they were part of NUnit itself.
[Test]
public void Test1()
{
Assert.That(5, Is.Prime);
Assert.That(4, Is.Not.Prime);
}