The Real Reason Your Tests Fail Consistently
A test passes, you merge it. Then, the next run fails. And the next. It’s a familiar cycle that often leads to tests being labeled "flaky." Developers might increase timeouts or add retries, hoping to smooth over the apparent randomness. But if a test consistently fails after a single passing run, it's not flaky. It's something far more insidious: it has corrupted its own environment or consumed its necessary data.
The distinction between flaky and state-corrupting tests is critical. Flaky tests fail and pass unpredictably due to race conditions, timing issues, or slow UI rendering. They are fixed by ensuring the test waits for the correct state. Tests that pass once and then fail consistently, however, indicate that the initial run permanently altered the system. This alteration could be the consumption of essential test data or the creation of leftover state that invalidates the test's preconditions for subsequent runs. Treating a state-corrupting test as flaky by simply adding retries or longer waits is like applying a band-aid to a broken bone – it masks the symptom while the underlying issue festers, leading to wasted developer time and a false sense of security.
Think of a test like a meticulous chef preparing a specific dish. A flaky test is like a chef who sometimes drops an ingredient or burns the toast – unpredictable, frustrating, but fixable by adjusting technique slightly. A state-corrupting test, however, is like a chef who, after successfully making the dish once, eats all the ingredients needed for the next preparation or leaves a huge mess that prevents any further cooking. The problem isn't the chef's skill; it's that the chef didn't clean up or account for the resources used.

Three Common Manifestations of State Corruption
Understanding how these tests corrupt their own environment is key to fixing them. There are typically three main ways this happens:
1. Data Consumption
The most straightforward form of state corruption occurs when a test consumes data it requires to run. Imagine a test designed to verify that a user can successfully create a new account. If the test creates this account and then proceeds to use it for subsequent assertions, it might inadvertently delete or modify that account in a way that makes it unusable for the *next* time the test runs. If the test doesn't clean up the created account, the subsequent test run will attempt to create an account that already exists, or it might fail because a prerequisite related to the account's existence is no longer met. The test succeeded on its first run because the data was available; it fails subsequently because that data is now gone or altered.
2. State Left Behind
Conversely, tests can fail because they leave behind artifacts that interfere with their own preconditions. Consider a test that verifies the functionality of a shopping cart. The test might add items to the cart, proceed through checkout, and then assert the order status. If the test doesn't properly clear the shopping cart after the assertion, the next test run will find items already in the cart. This can lead to unexpected behavior, such as the cart total being incorrect, checkout failing because of pre-existing items, or assertions about adding new items failing because the cart is already populated. The test's success on the first run was contingent on a clean slate; its subsequent failures are due to the lingering state from the previous execution.
3. External Dependencies Altered
Beyond the immediate data or state within the application under test, tests can also corrupt their own environment by altering external dependencies. This could involve modifying configuration files, changing database records in a shared test database, or altering the state of mock services. For example, a test that updates a configuration setting to enable a specific feature for its own execution might forget to revert that setting. The next test run, expecting the default configuration, will behave differently and potentially fail. Similarly, tests that write to a shared database might leave records that violate unique constraints or foreign key relationships for subsequent tests. The test environment itself becomes a variable, leading to unpredictable outcomes.
The Cost of Misdiagnosis
The temptation to label these consistent failures as
