How to Test Expected Exceptions in NUnit and xUnit

Testing for expected exceptions is a critical aspect of unit testing in C# to ensure your code fails gracefully and handles invalid inputs correctly. Both NUnit and xUnit provide built-in assertions designed to catch thrown exceptions, inspect their types, and evaluate their error messages. This guide covers how to implement synchronous and asynchronous exception tests in both frameworks using clean, standard syntax.

Testing Exceptions in NUnit

NUnit utilizes the Assert.Throws<T> method to verify that a specific delegate throws the expected exception type.

Synchronous Exceptions

To test a synchronous method, pass the executing code inside an Action delegate or lambda expression. Assert.Throws<T> returns the caught exception, allowing you to run further assertions on properties such as the error message.

using NUnit.Framework;
using System;

[TestFixture]
public class NUnitExceptionTests
{
    [Test]
    public void Divide_ByZero_ThrowsDivideByZeroException()
    {
        var calculator = new Calculator();

        var exception = Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));
        
        Assert.That(exception.Message, Is.EqualTo("Cannot divide by zero."));
    }
}

Alternatively, you can use the constraint-based model with Assert.That:

[Test]
public void Divide_ByZero_ThrowsDivideByZeroException_ConstraintModel()
{
    var calculator = new Calculator();

    Assert.That(() => calculator.Divide(10, 0), Throws.TypeOf<DivideByZeroException>());
}

Asynchronous Exceptions

For code that returns a Task, use Assert.ThrowsAsync<T>:

[Test]
public void FetchDataAsync_InvalidId_ThrowsArgumentException()
{
    var service = new DataService();

    var exception = Assert.ThrowsAsync<ArgumentException>(async () => await service.FetchDataAsync(-1));

    Assert.That(exception.ParamName, Is.EqualTo("id"));
}

Testing Exceptions in xUnit

xUnit also relies on an Assert.Throws<T> pattern, which is strictly generic and functional.

Synchronous Exceptions

In xUnit, Assert.Throws<T> intercepts the exception and returns the instance for message and property validation:

using System;
using Xunit;

public class XunitExceptionTests
{
    [Fact]
    public void Divide_ByZero_ThrowsDivideByZeroException()
    {
        var calculator = new Calculator();

        var exception = Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));

        Assert.Equal("Cannot divide by zero.", exception.Message);
    }
}

Asynchronous Exceptions

For asynchronous operations, xUnit provides Assert.ThrowsAsync<T>. Unlike NUnit, Assert.ThrowsAsync<T> returns a Task<TException>, so the test method must be marked async and the assertion itself must be awaited:

[Fact]
public async Task FetchDataAsync_InvalidId_ThrowsArgumentException()
{
    var service = new DataService();

    var exception = await Assert.ThrowsAsync<ArgumentException>(() => service.FetchDataAsync(-1));

    Assert.Equal("id", exception.ParamName);
}

Summary of Differences

  • Exact Types vs. Derived Types: Both Assert.Throws<T> in NUnit and xUnit match exact exception types. If you need to allow derived exception types, NUnit offers Throws.InstanceOf<T>(), while xUnit provides Assert.ThrowsAny<T>() and Assert.ThrowsAnyAsync<T>().
  • Async Awaiting: In xUnit, you await Assert.ThrowsAsync<T>(). In NUnit, Assert.ThrowsAsync<T>() handles the task execution internally and returns the exception synchronously.