Async Constraint Support
NUnit constraints fully support testing asynchronous code. This page covers patterns for using constraints with async methods, tasks, and delegates.
The examples on this page use this small service and calculator:
// A small service used by the examples below
public class SampleService
{
private int _readyChecks;
private readonly List<int> _cache = [];
public Task<int> GetValueAsync() => Task.FromResult(42);
public Task InitializeAsync() => Task.CompletedTask;
public Task SafeMethodAsync() => Task.CompletedTask;
public Task InvalidOperationAsync() => throw new InvalidOperationException("expected error");
public async Task FailingMethodAsync()
{
await Task.Yield();
throw new InvalidOperationException("expected error");
}
public Task FailingWithInnerExceptionAsync() =>
Task.FromException(new AggregateException(new TimeoutException()));
// Becomes ready on the third call
public Task<bool> IsReadyAsync() => Task.FromResult(++_readyChecks >= 3);
public Task<int> GetCountAsync()
{
_cache.Add(_cache.Count);
return Task.FromResult(_cache.Count);
}
public async IAsyncEnumerable<int> GetItemsAsync()
{
foreach (var item in new[] { 50, 150, 250, 350, 450 })
{
await Task.Yield();
yield return item;
}
}
}
public class Calculator
{
public Task<int> AddAsync(int a, int b) => Task.FromResult(a + b);
}
Testing Async Methods
Basic Async Assertions
Use async lambdas with Assert.That to test async code:
[Test]
public void BasicAsyncAssertions()
{
// Test async method return values
Assert.That(async () => await service.GetValueAsync(), Is.EqualTo(42));
// Test that async method completes without throwing
Assert.That(async () => await service.InitializeAsync(), Throws.Nothing);
// Test async exception throwing
Assert.That(async () => await service.InvalidOperationAsync(),
Throws.TypeOf<InvalidOperationException>());
}
Testing Task Results
You can also await tasks directly before asserting:
[Test]
public async Task TestingTaskResults()
{
// Await first, then assert
var result = await calculator.AddAsync(2, 3);
Assert.That(result, Is.EqualTo(5));
// Or use Assert.ThatAsync (NUnit 4.0+)
await Assert.ThatAsync(async () => await calculator.AddAsync(2, 3), Is.EqualTo(5));
}
Exception Testing with Async Code
ThrowsConstraint with Async
[Test]
public void ThrowsConstraintWithAsync()
{
// Test for specific exception type
Assert.That(async () => await service.FailingMethodAsync(),
Throws.TypeOf<InvalidOperationException>());
// Test exception message
Assert.That(async () => await service.FailingMethodAsync(),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("expected error"));
// Test inner exception
Assert.That(async () => await service.FailingWithInnerExceptionAsync(),
Throws.TypeOf<AggregateException>()
.With.InnerException.TypeOf<TimeoutException>());
}
ThrowsNothing with Async
[Test]
public void ThrowsNothingWithAsync()
{
// Verify async method completes successfully
Assert.That(async () => await service.SafeMethodAsync(), Throws.Nothing);
}
Delayed Constraints with Async
Use DelayedConstraint for testing eventually-consistent async operations:
[Test]
public void DelayedConstraintsWithAsync()
{
// Poll until condition is true (or timeout)
Assert.That(async () => await service.IsReadyAsync(),
Is.True.After(5).Seconds.PollEvery(100).MilliSeconds);
// Wait for value to appear
Assert.That(async () => await service.GetCountAsync(),
Is.GreaterThan(0).After(2000, 100));
}
Collection Constraints with Async Enumerables
For IAsyncEnumerable<T>, materialize the collection first. ToListAsync is built into .NET 10; on earlier
versions it comes from the System.Linq.Async package:
[Test]
public async Task CollectionConstraintsWithAsyncEnumerables()
{
// Convert to list first
var items = await service.GetItemsAsync().ToListAsync();
Assert.That(items, Has.Count.EqualTo(5));
Assert.That(items, Is.All.Positive);
// Or inline the conversion
Assert.That(await service.GetItemsAsync().ToListAsync(), Has.Some.GreaterThan(100));
}
Best Practices
Use async lambdas when testing methods that return
TaskorTask<T>:Assert.That(async () => await method(), constraint);Avoid blocking calls like
.Resultor.Wait()- they can cause deadlocks:// BAD - can deadlock Assert.That(service.GetValueAsync().Result, Is.EqualTo(42)); // GOOD - use async lambda Assert.That(async () => await service.GetValueAsync(), Is.EqualTo(42));Use
Throws.Nothingto explicitly verify async code doesn't throw:Assert.That(async () => await service.ProcessAsync(), Throws.Nothing);Use
DelayedConstraintfor eventually-consistent operations instead ofTask.Delay:// BAD - arbitrary wait await Task.Delay(1000); Assert.That(value, Is.True); // GOOD - polls until true or timeout Assert.That(() => value, Is.True.After(2).Seconds.PollEvery(100).MilliSeconds);
See Also
- Throws Constraint - Exception testing
- ThrowsNothing Constraint - Verify no exception
- Delayed Constraint - Polling and timeouts