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

Analyzing Test Flakiness

Implement systematic approaches to identify and resolve non-deterministic test failures.

In this lesson, you will learn how to transition from simply labeling tests as "flaky" to systematically isolating and eliminating the root causes of non-deterministic behavior in your C# test suite.

The Anatomy of Flakiness

A flaky test passes and fails intermittently without any changes to the underlying code. In a mature WebApplicationFactory or Testcontainers environment, flakiness is rarely "random." It is almost always a symptom of hidden side effects, such as race conditions, shared mutable state, or non-deterministic ordering in asynchronous background tasks.

Systematic Investigation

When a test fails intermittently, stop relying on "re-running the pipeline." Instead, follow these steps:

  1. Isolation via Quarantine: Isolate the failing test into a specific suite and execute it in a loop (e.g., 100 times) within your local environment. If it doesn't fail locally, the issue likely resides in the environment configuration (like resource contention on your CI runner).
  2. Audit Async Synchronization: Often, tests fail because of an assertion that checks a database state before an IHostedService has finished processing a background task. Use Polly or a polling mechanism with a timeout to wait for the expected state instead of using Thread.Sleep().
  3. Identify Shared State: Ensure that Testcontainers are cleaned up correctly between runs. If tests share a database schema or a cached object, verify that every test triggers a clean state using the transaction strategies we previously discussed.

Example: Identifying Race Conditions

If a test verifying a message queue processor fails intermittently, it is likely because the assertion executes before the consumer consumes the message.

// Instead of a fragile Task.Delay(1000)
var result = await WaitUntilAsync(async () => 
    await repository.GetByIdAsync(id), 
    expected: "Processed", 
    timeout: TimeSpan.FromSeconds(5));

Assert.Equal("Processed", result.Status);

By replacing arbitrary delays with event-driven or polling-based assertions, you remove the reliance on the execution speed of the CI server, turning a non-deterministic result into a deterministic one. Once you identify that a test is consistently failing under specific high-load conditions, you can refactor the system architecture to remove the race condition entirely.

Key Takeaway: Flakiness is a data-gathering exercise; replace arbitrary sleeps with state-polling and ensure absolute isolation of test infrastructure to convert non-deterministic failures into repeatable debugging scenarios.