Test Order Dependency.
A test passes or fails depending on which other tests ran before it, because tests leak and rely on shared mutable state instead of each setting up and tearing down its own fixture.
##Signs and Symptoms
A test's result depends on the order in which the suite runs, not only on the code under test. Each test is supposed to be self-contained, but here one test silently relies on side effects left behind by another.
Tell-tale signs:
- A test passes in the full suite but fails when run alone (via
.only, a name filter, or running just that file). Meszaros calls this a Lonely Test. - Tests break when you shuffle, shard, or parallelize the run, or after upgrading a runner that changes default order.
- Deleting, skipping, or reordering one test makes a seemingly unrelated test fail.
- One failure triggers a cascade of downstream failures (Meszaros's Interacting Tests; van Deursen's Test Run War when parallel runners collide over a shared fixture).
- Tests are intermittently flaky with no code change — order-dependence is one of the most common root causes of flaky tests.
The structural giveaway is shared mutable state that is read across tests: module-level/static/global variables, a beforeAll that populates state once, or an un-reset external resource (DB rows, files, caches, env vars, fake timers, mocks).
let users = []; // shared module-level state
test('creates a user', () => {
users.push({ id: 1, name: 'Ada' });
expect(users).toHaveLength(1);
});
// Green only because the test above ran first and mutated `users`.
// Run this test alone, or shuffle the order, and it fails.
test('finds the created user', () => {
expect(users.find(u => u.name === 'Ada')).toBeDefined();
});
##Reasons for the Problem
Why it happens
- A Shared Fixture (set up once in
beforeAll, a module-scoped variable, a classstatic, or a real database/file) is mutated by tests and never reset between them, so each test inherits the previous one's leftovers. - Convenience and speed: people reuse expensive setup or "build on" the previous test's data to avoid re-creating it (sometimes formalized as Chained Tests).
- The dependency is accidental and invisible — nothing in the test code declares "run me after that one," so it survives until the order changes.
Why it hurts
- Reliability / false confidence. The suite is green by luck of ordering. Reorder, parallelize, or run a subset and tests fail (or, worse, a real regression hides because an earlier test happened to leave "correct" state behind). Order-dependent tests are a leading source of flaky tests.
- Maintainability. You can't run, debug, or rerun a single failing test in isolation — the prerequisite test must run first. Adding, removing, or reordering tests has spooky action at a distance.
- Readability. A test no longer documents one behavior with explicit inputs; understanding it requires reading whatever ran before it (a Mystery Guest hidden in execution order).
- Blocks parallelism and selection. Test sharding, parallel runners, and test-impact/selection tooling all assume independence; order coupling makes them unsafe.
##Treatment
Make every test independent: it sets up everything it needs, asserts, and cleans up, so it produces the same result in any order, alone or in a suite.
- Give each test a Fresh Fixture. Move shared setup out of
beforeAllintobeforeEach(or build it inside the test), so state is reconstructed per test rather than accumulated. - Eliminate shared mutable state. Don't read/write module-level,
static, or global variables across tests. Construct objects locally; pass data in explicitly. - Reset external resources in teardown. Roll back the DB (transaction per test) or use unique data per test; delete temp files; restore env vars, globals, and fake timers; clear mocks (
jest.clearAllMocks()/vi.restoreAllMocks(),jest.resetModules()). Prefer automatic/guaranteed teardown over manual cleanup. - Prove independence by randomizing order. This is the real detector — it is a dynamic property, not something a linter can see:
- Jest:
--randomize/randomize: true. - Vitest:
sequence.shuffle(config or--sequence.shuffle). - pytest:
pytest-randomlyorpytest-random-order. - Maven Surefire:
-Dsurefire.runOrder=random. Run a subset/single test in CI too, so Lonely Tests surface.
- Jest:
- If shared setup is genuinely needed (expensive, read-only fixture), make it immutable and shared read-only, or use a deliberately-documented Chained Test suite as a last resort — never an accidental dependency. Note that
eslint-plugin-jest/eslint-plugin-vitest'sno-hooksrule can discourage the setup/teardown hooks that tend to promote shared state, but it does not detect order dependency itself.
// Before — order-dependent: test 2 needs test 1's leftovers
let users = [];
test('creates a user', () => {
users.push({ id: 1, name: 'Ada' });
expect(users).toHaveLength(1);
});
test('finds the created user', () => {
expect(users.find(u => u.name === 'Ada')).toBeDefined();
});
// After — each test owns its fixture
function makeRepo(seed = []) {
return { users: [...seed] };
}
test('creates a user', () => {
const repo = makeRepo();
repo.users.push({ id: 1, name: 'Ada' });
expect(repo.users).toHaveLength(1);
});
test('finds an existing user', () => {
const repo = makeRepo([{ id: 1, name: 'Ada' }]); // sets up its own precondition
expect(repo.users.find(u => u.name === 'Ada')).toBeDefined();
});