Skip to main content

Awaiting

In TUnit you await your assertions, and this serves two purposes:

  • the await keyword is responsible for performing the assertion, before you call await we're building a chain of assertion rules.
  • it allows executing and asserting on async delegates without performing sync-over-async

Because of this, your tests should be async and return a Task.

Don't worry about forgetting to await - There's an analyzer built in that will notify you if you've missed any!
If you forget to await, your assertion will not actually be executed, and your test may pass when it should fail.

This will error:

    [Test]
public void MyTest()
{
var result = Add(1, 2);

Assert.That(result).IsEqualTo(3);
}

This won't:

    [Test]
public async Task MyTest()
{
var result = Add(1, 2);

await Assert.That(result).IsEqualTo(3);
}

TUnit is able to take in asynchronous delegates. To be able to assert on these, we need to execute the code. We want to avoid sync-over-async, as this can cause problems and block the thread pool, slowing down your test suite. And with how fast .NET has become, the overhead of Tasks and async methods shouldn't be noticeable.

Complex Assertion Examples

Chaining Multiple Assertions

You can chain multiple assertions together for more complex validations:

[Test]
public async Task ComplexObjectValidation()
{
var user = await GetUserAsync("john.doe");

// Chain multiple property assertions
await Assert.That(user)
.IsNotNull()
.And.HasProperty(u => u.Email).EqualTo("john.doe@example.com")
.And.HasProperty(u => u.Age).GreaterThan(18)
.And.HasProperty(u => u.Roles).Contains("Admin");
}

Collection Assertions with Complex Conditions

[Test]
public async Task ComplexCollectionAssertions()
{
var orders = await GetOrdersAsync();

// Assert multiple conditions on a collection
await Assert.That(orders)
.HasCount().GreaterThan(0)
.And.Contains(o => o.Status == OrderStatus.Completed)
.And.DoesNotContain(o => o.Total < 0)
.And.HasDistinctItems(new OrderIdComparer());

// Assert on filtered subset
var completedOrders = orders.Where(o => o.Status == OrderStatus.Completed);
await Assert.That(completedOrders)
.All(o => o.CompletedDate != null)
.And.Any(o => o.Total > 1000);
}

Async Operation Assertions

[Test]
public async Task AsyncOperationAssertions()
{
// Assert that async operation completes within time limit
await Assert.That(async () => await LongRunningOperationAsync())
.CompletesWithin(TimeSpan.FromSeconds(5));

// Assert that async operation throws specific exception
await Assert.That(async () => await RiskyOperationAsync())
.Throws<InvalidOperationException>()
.WithMessage().Containing("connection failed");

// Assert on result of async operation
await Assert.That(() => CalculateAsync(10, 20))
.ResultsIn(30)
.Within(TimeSpan.FromSeconds(1));
}

Nested Object Assertions

[Test]
public async Task NestedObjectAssertions()
{
var company = await GetCompanyAsync();

await Assert.That(company)
.IsNotNull()
.And.HasProperty(c => c.Name).EqualTo("TechCorp")
.And.HasProperty(c => c.Address).Satisfies(address =>
Assert.That(address)
.HasProperty(a => a.City).EqualTo("Seattle")
.And.HasProperty(a => a.ZipCode).Matches(@"^\d{5}$")
)
.And.HasProperty(c => c.Employees).Satisfies(employees =>
Assert.That(employees)
.HasCount().Between(100, 500)
.And.All(e => e.Email.EndsWith("@techcorp.com"))
);
}

Exception Assertions with Details

[Test]
public async Task DetailedExceptionAssertions()
{
var invalidData = new { Id = -1, Name = "" };

// Assert exception with specific properties
await Assert.That(() => ProcessDataAsync(invalidData))
.Throws<ValidationException>()
.WithMessage().EqualTo("Validation failed")
.And.WithInnerException<ArgumentException>()
.WithParameterName("data");

// Assert aggregate exception
await Assert.That(() => ParallelOperationAsync())
.Throws<AggregateException>()
.Where(ex => ex.InnerExceptions.Count == 3)
.And.Where(ex => ex.InnerExceptions.All(e => e is TaskCanceledException));
}

Custom Assertion Conditions

[Test]
public async Task CustomAssertionConditions()
{
var measurements = await GetMeasurementsAsync();

// Use custom conditions for complex validations
await Assert.That(measurements)
.Satisfies(m => {
var average = m.Average();
var stdDev = CalculateStandardDeviation(m);
return stdDev < average * 0.1; // Less than 10% deviation
}, "Measurements should have low standard deviation");

// Combine built-in and custom assertions
await Assert.That(measurements)
.HasCount().GreaterThan(100)
.And.All(m => m > 0)
.And.Satisfies(IsNormallyDistributed, "Data should be normally distributed");
}

DateTime and TimeSpan Assertions

[Test]
public async Task DateTimeAssertions()
{
var order = await CreateOrderAsync();

// Complex datetime assertions
await Assert.That(order.CreatedAt)
.IsAfter(DateTime.UtcNow.AddMinutes(-1))
.And.IsBefore(DateTime.UtcNow.AddMinutes(1))
.And.HasKind(DateTimeKind.Utc);

// TimeSpan assertions
var processingTime = order.CompletedAt - order.CreatedAt;
await Assert.That(processingTime)
.IsLessThan(TimeSpan.FromMinutes(5))
.And.IsGreaterThan(TimeSpan.Zero);
}

Floating Point Comparisons

[Test]
public async Task FloatingPointAssertions()
{
var calculations = await PerformComplexCalculationsAsync();

// Use tolerance for floating point comparisons
await Assert.That(calculations.Pi)
.IsEqualTo(Math.PI).Within(0.0001);

// Assert on collections of floating point numbers
await Assert.That(calculations.Results)
.All(r => Math.Abs(r) < 1000000) // No overflow
.And.Contains(42.0).Within(0.1) // Contains approximately 42
.And.HasSum().EqualTo(expectedSum).Within(0.01);
}

String Pattern Matching

[Test]
public async Task StringPatternAssertions()
{
var logs = await GetLogEntriesAsync();

// Complex string assertions
await Assert.That(logs)
.All(log => log.Matches(@"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]"))
.And.Any(log => log.Contains("ERROR"))
.And.None(log => log.Contains("SENSITIVE_DATA"));

// Assert on formatted output
var report = await GenerateReportAsync();
await Assert.That(report)
.StartsWith("Report Generated:")
.And.Contains("Total Items:")
.And.DoesNotContain("null")
.And.HasLength().Between(1000, 5000);
}

Combining Or and And Conditions

[Test]
public async Task ComplexLogicalConditions()
{
var product = await GetProductAsync();

// Complex logical combinations
await Assert.That(product)
.HasProperty(p => p.Status)
.EqualTo(ProductStatus.Active)
.Or.EqualTo(ProductStatus.Pending)
.And.HasProperty(p => p.Price)
.GreaterThan(0)
.And.LessThan(10000);

// Multiple condition paths
await Assert.That(product.Category)
.IsEqualTo("Electronics")
.And(Assert.That(product.Warranty).IsNotNull())
.Or
.IsEqualTo("Books")
.And(Assert.That(product.ISBN).IsNotNull());
}

Performance Assertions

[Test]
public async Task PerformanceAssertions()
{
var stopwatch = Stopwatch.StartNew();
var results = new List<long>();

// Measure multiple operations
for (int i = 0; i < 100; i++)
{
var start = stopwatch.ElapsedMilliseconds;
await PerformOperationAsync();
results.Add(stopwatch.ElapsedMilliseconds - start);
}

// Assert on performance metrics
await Assert.That(results.Average())
.IsLessThan(100); // Average under 100ms

await Assert.That(results.Max())
.IsLessThan(500); // No operation over 500ms

await Assert.That(results.Where(r => r > 200).Count())
.IsLessThan(5); // Less than 5% over 200ms
}

State Machine Assertions

[Test]
public async Task StateMachineAssertions()
{
var workflow = new OrderWorkflow();

// Initial state
await Assert.That(workflow.State).IsEqualTo(OrderState.New);

// State transition assertions
await workflow.StartProcessing();
await Assert.That(workflow.State)
.IsEqualTo(OrderState.Processing)
.And(Assert.That(workflow.CanTransitionTo(OrderState.Completed)).IsTrue())
.And(Assert.That(workflow.CanTransitionTo(OrderState.New)).IsFalse());

// Complex workflow validation
await workflow.Complete();
await Assert.That(workflow)
.HasProperty(w => w.State).EqualTo(OrderState.Completed)
.And.HasProperty(w => w.CompletedAt).IsNotNull()
.And.HasProperty(w => w.History).Contains(h => h.State == OrderState.Processing);
}

These examples demonstrate the power and flexibility of TUnit's assertion system, showing how you can build complex, readable assertions for various testing scenarios.