Course contentsShow
← C#
Lesson 1676 of 1,90815. Software Quality and TestingFree lesson

Verification of Eventual Consistency

Implement polling-based assertions to verify state changes in asynchronous systems.

In this lesson, you will learn how to implement polling-based assertions to verify that asynchronous state changes eventually reach an expected value in your distributed systems.

The Challenge of Asynchrony

When working with background tasks, message queues, or distributed services, state changes rarely happen instantly. A standard Assert.Equal() often fails because it executes the moment the service request completes, before the background worker has finished processing the event. Hard-coding a Thread.Sleep() is a "test smell" that leads to brittle, slow test suites. Instead, we use polling-based assertions.

Implementing Polling with Assertions

The goal is to repeatedly poll the system state until it matches your expectation or a timeout is reached. This balances test performance (finishing as soon as the work is done) with reliability (waiting just long enough for the eventual consistency to resolve).

Here is a simple implementation pattern using a generic polling helper:

public async Task ShouldUpdateUserStatusEventually(Guid userId)
{
    var timeout = TimeSpan.FromSeconds(5);
    var interval = TimeSpan.FromMilliseconds(200);
    
    // Poll the database or API until the expected state is reached
    var success = await PollingHelper.Until(
        async () => await _dbContext.Users.FindAsync(userId),
        user => user.Status == "Verified",
        timeout,
        interval);

    Assert.True(success, "The user status did not become 'Verified' within the timeout.");
}

The Polling Logic

The PollingHelper logic should look like this:

public static async Task<bool> Until<T>(Func<Task<T>> getter, Func<T, bool> condition, TimeSpan timeout, TimeSpan interval)
{
    var cts = new CancellationTokenSource(timeout);
    while (!cts.IsCancellationRequested)
    {
        var result = await getter();
        if (condition(result)) return true;
        await Task.Delay(interval);
    }
    return false;
}

This approach allows your integration tests to remain deterministic despite the non-deterministic timing of background message processing. By checking the state in intervals rather than pausing execution indefinitely, you ensure your test suite remains performant while accurately verifying that your system eventually converges on the correct state.

Key Takeaway: Replace rigid delays with polling-based assertions to robustly verify asynchronous state transitions without sacrificing test execution speed.