How to debug flaky tests -- tests that pass and fail randomly
flaky tests, timing dependencies, test isolation failures, shared state, async race conditions in tests, quarantining flaky tests
Flaky Tests Are Bugs in Your Tests
A test that sometimes passes and sometimes fails is not giving you reliable information. It wastes time, erodes trust in the test suite, and often indicates a real production bug -- one that occurs intermittently in the same way. Treat a flaky test as a bug to fix, not noise to ignore.
The Three Root Causes
Flaky tests have three common causes. First: shared state between tests -- one test modifies global data that another test expects to be fresh. Fix: reset all shared state in a beforeEach hook. Second: timing dependency -- a test assumes an async operation completes in a certain time. Fix: await the operation explicitly, do not use setTimeout to wait. Third: dependency on external services -- network calls return different results. Fix: mock external dependencies.
// Flaky: depends on timing
it('processes request', () => {
submitRequest();
setTimeout(() => {
expect(result).toBe('done'); // fails if processing takes longer
}, 100);
});
// Fixed: await properly
it('processes request', async () => {
await submitRequest();
expect(result).toBe('done'); // deterministic
});
// Shared state fix
let db;
beforeEach(async () => {
db = await createTestDatabase(); // fresh DB per test
});
afterEach(async () => {
await db.destroy();
});
Quarantine Before Fix
If a flaky test is blocking CI, quarantine it -- mark it as skipped with a bug ticket reference -- rather than deleting it. It documents a real intermittent issue. Fix the underlying cause (shared state, timing, external dependency) and then restore the test.
