xunit
Assert.ThrowsAsync
unit testing
async methods
debugging

xunit Assert.ThrowsAsync does not work properly?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Assert.ThrowsAsync<T> in xUnit verifies that an async method throws a specific exception type. The most common reason it "does not work" is that the test method does not await the Assert.ThrowsAsync call. Without await, the test runner sees a completed test (no exception) while the assertion runs in the background. The second common cause is passing a synchronous lambda instead of one that returns a Task. Both issues cause the test to pass incorrectly, hiding real bugs.

The Problem: Missing await

csharp
1// BROKEN — test always passes regardless of exception
2[Fact]
3public async Task Should_Throw_InvalidOperation()
4{
5    // Missing await! Assert.ThrowsAsync returns a Task that is never awaited
6    Assert.ThrowsAsync<InvalidOperationException>(
7        () => myService.ProcessAsync(null)
8    );
9    // Test exits here — the assertion never completes
10}
11
12// FIX — await the assertion
13[Fact]
14public async Task Should_Throw_InvalidOperation()
15{
16    await Assert.ThrowsAsync<InvalidOperationException>(
17        () => myService.ProcessAsync(null)
18    );
19}

Assert.ThrowsAsync returns a Task<T>. If you do not await it, the test method returns before the assertion evaluates. The test runner sees no exception from the test method and marks it as passed.

Correct Usage Patterns

csharp
1public class OrderService
2{
3    public async Task<Order> CreateOrderAsync(OrderRequest request)
4    {
5        if (request == null)
6            throw new ArgumentNullException(nameof(request));
7
8        if (request.Items.Count == 0)
9            throw new InvalidOperationException("Order must have items");
10
11        return await _repository.SaveAsync(new Order(request));
12    }
13}
14
15// Test: verify specific exception type
16[Fact]
17public async Task CreateOrder_NullRequest_ThrowsArgumentNull()
18{
19    var service = new OrderService();
20
21    await Assert.ThrowsAsync<ArgumentNullException>(
22        () => service.CreateOrderAsync(null)
23    );
24}
25
26// Test: verify exception message
27[Fact]
28public async Task CreateOrder_EmptyItems_ThrowsWithMessage()
29{
30    var service = new OrderService();
31    var request = new OrderRequest { Items = new List<Item>() };
32
33    var ex = await Assert.ThrowsAsync<InvalidOperationException>(
34        () => service.CreateOrderAsync(request)
35    );
36
37    Assert.Equal("Order must have items", ex.Message);
38}

Assert.ThrowsAsync returns the caught exception, so you can inspect its properties (message, inner exception, custom fields) after awaiting.

ThrowsAsync vs ThrowsAnyAsync

csharp
1// ThrowsAsync<T> — requires EXACT exception type
2// Fails if a derived exception is thrown
3[Fact]
4public async Task Throws_ExactType()
5{
6    // If method throws ArgumentNullException (derives from ArgumentException),
7    // Assert.ThrowsAsync<ArgumentException> FAILS
8    await Assert.ThrowsAsync<ArgumentException>(
9        () => service.ValidateAsync(null)
10    );
11    // Fails because ArgumentNullException != ArgumentException
12}
13
14// ThrowsAnyAsync<T> — accepts T or any derived type
15[Fact]
16public async Task ThrowsAny_DerivedType()
17{
18    await Assert.ThrowsAnyAsync<ArgumentException>(
19        () => service.ValidateAsync(null)
20    );
21    // Passes for ArgumentException, ArgumentNullException,
22    // ArgumentOutOfRangeException, etc.
23}

Use ThrowsAsync<T> when you expect an exact exception type. Use ThrowsAnyAsync<T> when derived types should also satisfy the assertion.

Synchronous Exceptions in Async Methods

csharp
1// Method throws BEFORE any await
2public async Task<int> DivideAsync(int a, int b)
3{
4    if (b == 0)
5        throw new DivideByZeroException();  // Thrown synchronously
6
7    return await Task.FromResult(a / b);
8}
9
10// Assert.ThrowsAsync still works — it handles both cases
11[Fact]
12public async Task Divide_ByZero_Throws()
13{
14    var calc = new Calculator();
15
16    await Assert.ThrowsAsync<DivideByZeroException>(
17        () => calc.DivideAsync(10, 0)
18    );
19    // Works because the lambda returns a faulted Task
20}

When an async method throws before its first await, the exception is captured in the returned Task. Assert.ThrowsAsync correctly handles this case.

Testing with async Lambdas

csharp
1// Use async lambda when you need await inside the test action
2[Fact]
3public async Task ComplexOperation_ThrowsOnSecondStep()
4{
5    await Assert.ThrowsAsync<TimeoutException>(async () =>
6    {
7        var connection = await CreateConnectionAsync();
8        await connection.ExecuteAsync("LONG QUERY");  // This throws
9    });
10}
11
12// The lambda MUST return Task (or be async)
13// BROKEN — synchronous lambda
14[Fact]
15public async Task Wrong_SyncLambda()
16{
17    await Assert.ThrowsAsync<InvalidOperationException>(() =>
18    {
19        service.SyncMethod();  // Does not return Task
20        return Task.CompletedTask;  // Never faults
21    });
22    // Test fails — Task.CompletedTask has no exception
23}

Record Pattern (Alternative)

csharp
1// Capture exception for multiple assertions
2[Fact]
3public async Task CreateOrder_InvalidData_ThrowsWithDetails()
4{
5    var service = new OrderService();
6
7    var exception = await Record.ExceptionAsync(
8        () => service.CreateOrderAsync(new OrderRequest { Items = null })
9    );
10
11    Assert.NotNull(exception);
12    Assert.IsType<ValidationException>(exception);
13
14    var validationEx = (ValidationException)exception;
15    Assert.Contains("Items", validationEx.Errors.Keys);
16}

Record.ExceptionAsync captures any exception without asserting its type, giving you full control over what to check.

Common Pitfalls

  • Not awaiting Assert.ThrowsAsync: This is the number one cause of "it does not work." Without await, the test method returns immediately and passes. The assertion runs as an unobserved Task. Always await Assert.ThrowsAsync<T>(...).
  • Using ThrowsAsync when a derived exception is thrown: ThrowsAsync<ArgumentException> fails if the method throws ArgumentNullException (which derives from ArgumentException). Use ThrowsAnyAsync<ArgumentException> to accept derived types.
  • Returning Task.CompletedTask from the lambda: If your lambda catches the exception internally or returns a non-faulted task, ThrowsAsync sees a successful completion and fails the test. Ensure the exception propagates into the returned Task.
  • Mixing Assert.Throws with async methods: Assert.Throws<T> (synchronous) does not await the returned Task. It only catches exceptions thrown synchronously during Task creation. Use Assert.ThrowsAsync<T> for all async methods.
  • Compiler warning CS4014 (unawaited task): If you forget await, the C# compiler warns "this call is not awaited." Treat CS4014 as an error in test projects by adding <WarningsAsErrors>CS4014</WarningsAsErrors> to your .csproj to catch missing awaits at build time.

Summary

  • Always await the call to Assert.ThrowsAsync<T>() — without it, the test always passes
  • Use ThrowsAsync<T> for exact exception types, ThrowsAnyAsync<T> for derived types
  • The lambda passed to ThrowsAsync must return a Task (use async lambda or return the async method call)
  • Use Record.ExceptionAsync when you need to inspect multiple properties of the caught exception
  • Enable CS4014 as a build error to catch missing await on assertions at compile time

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.