Using NUnit 4.2, .NET 8
I can output my own custom exception message when it fails by using the message
parameter in Assert.That()
. This message gets displayed when the assert fails.
However, i'm looking for a way to output some messages even when the assert is a success. My use-case is that I have a lot of asserts in a single test method. And I'd like to be able to track what is being asserted in each test method from the CI pipeline.
TestContext.Out.WriteLine
works to output logs even on success, I thought i'd extend the Assert.That
method so that I don't have a bunch of TestContext.Out.WriteLines
after every Assert, but I can't seem to get it to work. (C# skill issues)
[Test]
public void Add_Two_Numbers()
{
int a = 2, b = 3, c = 4, d = 5;
int resultAB = a + b;
int resultCD = c + d;
Assert.Multiple(() =>
{
Assert.That(resultAB, Is.EqualTo(5), message: "Somehow it's not 5", assertDescription: $"Checking if {a}+{b} is equal to 5");
Assert.That(resultCD, Is.EqualTo(9), message: "Somehow it's not 9", assertDescription: $"Checking if {c}+{d} is equal to 9");
});
}
Questions:
Assert.That
with a new parameter.Assert.Multiple()
scoped since, that doesn't throw exceptions.Note: Breaking it down to multiple Test methods is not an option for my particular use-case.
Assert
. This will satisfy your proposed usage:using System.Runtime.CompilerServices;
using NUnit.Framework.Constraints;
public class Assert : NUnit.Framework.Assert
{
public static void That<T>(
T actual,
IResolveConstraint expression,
string message,
string assertDescription,
[CallerArgumentExpression(nameof(actual))] string actualExpression = "",
[CallerArgumentExpression(nameof(expression))] string constraintExpression = "")
{
#pragma warning disable NUnit2050 // False warning when we specify actualExpression & constraintExpression
That(actual, expression, message, actualExpression: actualExpression, constraintExpression: constraintExpression);
#pragma warning restore NUnit2050
TestContext.Out.WriteLine(assertDescription);
}
}
public class Tester {
[Test] public void Test1() {
int a = 0, b = 3, c = 4, d = 5;
Assert.Multiple(() => {
Assert.That(a + b, Is.EqualTo(5), message: "Somehow it's not 5", assertDescription: $"Checking if {a}+{b} is equal to 5");
Assert.That(c + d, Is.EqualTo(9), message: "Somehow it's not 9", assertDescription: $"Checking if {c}+{d} is equal to 9");
});
}
}
private
.using System.Reflection;
using System.Runtime.CompilerServices;
using NUnit.Framework.Constraints;
public class Assert : NUnit.Framework.Assert
{
private static readonly MethodInfo? ReportFailureMethod;
static Assert() // Static constructor
{
// The ReportFailure() method is a private,
// so we need to backdoor it via Reflection.
ReportFailureMethod = typeof(NUnit.Framework.Assert).GetMethod(
"ReportFailure",
BindingFlags.NonPublic | BindingFlags.Static,
[typeof(ConstraintResult), typeof(string), typeof(string), typeof(string)]);
}
public static void That<T>(
T actual,
IResolveConstraint expression,
string message,
string assertDescription,
[CallerArgumentExpression(nameof(actual))] string actualExpression = "",
[CallerArgumentExpression(nameof(expression))] string constraintExpression = "")
{
// Emulate what the actual Assert.That() does:
var constraint = expression.Resolve();
var result = constraint.ApplyTo(actual);
if (result.IsSuccess)
TestContext.Out.WriteLine($"Success: {assertDescription}");
else
ReportFailureMethod?.Invoke(null, [result, message.ToString(), actualExpression, constraintExpression]);
}
}
Unfortunately the above messes the stack trace logging. But it's still the closest we could get to emulating the original Assert.That()
while catching a successful assert.