Exceptions
There are three ways to assert on exceptions, depending on whether the code under test is synchronous or asynchronous, and whether you already have an exception instance.
Actions (Synchronous)
Call .Should() on an Action to get ActionAssertions:
Action throws = () => throw new InvalidOperationException("oops");
throws.Should().Throw<InvalidOperationException>();
Assert that no exception is thrown:
Action safe = () => { /* ... */ };
safe.Should().NotThrow();
Asserting the Message
throws.Should().Throw<InvalidOperationException>("oops");
Asserting Message and Inner Exception
var inner = new Exception("inner");
Action nested = () => throw new InvalidOperationException("outer", inner);
nested.Should().Throw<InvalidOperationException>("outer", inner);
Asserting a Specific Exception Instance
var expected = new InvalidOperationException("oops");
Action throws = () => throw expected;
throws.Should().Throw(expected);
ArgumentException Shorthand
Action badArg = () => throw new ArgumentException("must be positive", "count");
badArg.Should().ThrowArgumentException("must be positive", "count");
ArgumentOutOfRangeException Shorthand
Action outOfRange = () => throw new ArgumentOutOfRangeException("count", -1, "must be positive");
outOfRange.Should().ThrowArgumentOutOfRangeException("must be positive", "count", -1);
See ThrowArgumentOutOfRangeException.
Async Actions
Call .Should() on a Func<Task> to get AsyncActionAssertions. All methods return Task and should be awaited:
Func<Task> throwsAsync = async () =>
{
await Task.Delay(1);
throw new InvalidOperationException("oops");
};
await throwsAsync.Should().ThrowAsync<InvalidOperationException>();
await throwsAsync.Should().ThrowAsync<InvalidOperationException>("oops");
await throwsAsync.Should().ThrowArgumentExceptionAsync("bad arg", "paramName");
await throwsAsync.Should().ThrowArgumentOutOfRangeExceptionAsync("out of range", "paramName", -1);
Func<Task> safeAsync = async () => await Task.Delay(1);
await safeAsync.Should().NotThrowAsync();
The Invoking / Awaiting Pattern
Use Invoking and Awaiting extension methods from InvokingExtensions to test methods on an object without creating a separate Action variable:
var parser = new Parser();
// Synchronous
parser.Invoking(p => p.Parse(null!))
.Should().ThrowArgumentException("Value cannot be null", "input");
// Async
await parser.Awaiting(p => p.ParseAsync(null!))
.Should().ThrowArgumentExceptionAsync("Value cannot be null", "input");
If the method returns a value, use the overloads that discard the result:
parser.Invoking(p => p.TryParse(null!, out _))
.Should().NotThrow();
Asserting on an Exception Directly
Call .Should() on an Exception instance to get ExceptionAssertions<T>:
var exception = new InvalidOperationException("oops");
exception.Should().HaveMessage("oops");
exception.Should().NotHaveMessage("something else");
exception.Should().HaveMessageStartingWith("oo");
Inner Exceptions
var inner = new ArgumentException("inner");
var outer = new InvalidOperationException("outer", inner);
outer.Should().HaveInnerException<ArgumentException>().And.HaveMessage("inner");
// Assert a specific instance
outer.Should().HaveInnerException(inner);
// Assert no inner exception
new Exception("no inner").Should().NotHaveInnerException();
See HaveInnerException and NotHaveInnerException.
Chaining After Throw
Throw<T> and ThrowAsync<T> return an ActionAssertionsChain<TException> with .Exception and .That properties for further assertions:
var chain = throws.Should().Throw<ArgumentException>();
// .Exception gives the thrown exception
chain.Exception.Should().HaveMessage("bad argument");
// .That gives ExceptionAssertions<T> directly for chaining
throws.Should().Throw<ArgumentException>()
.And.HaveMessage("bad argument")
.And.HaveParamName("value");