Quick answer: Tests that pass only when run in a certain order are sharing mutable state, and the most common culprit is a fixture that one test mutates and a later test assumes is pristine. The fix is not to enforce an order; it is to give each test isolated data it owns and can change freely. A test should be able to run alone, in parallel, or in any sequence and produce the same result. When every test starts from a fresh fixture or a dedicated row, order-dependent failures disappear and the suite becomes safe to parallelize.
The symptom is familiar and maddening. The whole suite is green, but a single test fails when run by itself, or the run goes red when tests are shuffled or parallelized. Root-causing it means finding the shared object, database row, or cached value that a previous test left in a modified state. The deeper problem is that such tests assert against a baseline they do not control, so any change in timing or ordering changes the result. Treating isolation as a hard requirement, rather than a nice-to-have, is what makes a test suite trustworthy.
Where shared state leaks in
Four places account for most order dependence, and identifying the layer determines the fix.
| Shared state | How it leaks | Isolation fix |
|---|---|---|
| In-memory fixture | Object mutated by reference | Deep clone per test |
| Database row | Same seed row updated | Unique row or transaction rollback |
| Module-level cache | Persists across tests | Reset in setup |
| Singleton service | State accumulates | New instance per test |
Clone fixtures, never share a reference
A fixture object imported once and reused is shared by reference, so a test that pushes to an array or changes a field affects every later test. Build a fresh copy in setup so each test gets its own object.
const baseOrder = Object.freeze({ id: "ord_1", status: "pending", items: Object.freeze([{ sku: "sku_1", qty: 1 }]), }); function makeOrder(overrides = {}) { // structuredClone gives a deep, independent copy return structuredClone({ ...baseOrder, ...overrides }); } beforeEach(() => { order = makeOrder(); // fresh object every test }); it("marks the order captured without touching the fixture", () => { order.status = "captured"; expect(order.status).toBe("captured"); });
Isolate database-backed tests
For integration tests, either wrap each test in a transaction that rolls back, or give each test a uniquely identified row so two tests never contend for the same record. Rows keyed by a test-specific id make parallel execution safe.
it("updates only its own order, even in parallel", async () => { const uniqueId =ord_${process.env.JEST_WORKER_ID}_${Date.now()}; await db.orders.insert({ id: uniqueId, status: "pending" }); await orderService.capture(uniqueId); const row = await db.orders.findById(uniqueId); expect(row.status).toBe("captured"); });
Prove isolation rather than assume it
The strongest check is to run the suite in random order and in parallel in CI. A test that depends on a prior test's state will fail under shuffling, exposing the hidden coupling instead of letting it linger. Combined with fresh fixtures, rollback or unique rows, and explicit cache resets, this turns isolation from a discipline into something the build enforces. The payoff is a suite that runs faster in parallel and never again fails because of which test happened to go first.
Frequently asked questions
Is it ever acceptable for tests to share state?
Not for independent assertions. A test may set up its own multi-step sequence, but that state belongs to the test and is created and torn down within it. Sharing across unrelated tests is the failure mode.
Why not just force a fixed execution order?
That locks in the coupling, blocks parallelism, and still fails when a test is run alone. Ordering hides the bug; isolation removes it.
Are deep copies expensive for large fixtures?
For most test fixtures the cost is negligible and far cheaper than debugging order-dependent failures. If a fixture is genuinely large, build only the fields each test needs.
How do I reset a module cache reliably?
Use the runner's reset mechanism in setup, or import the module inside a fresh test context rather than at the top level. Explicitly resetting singletons in beforeEach prevents state from accumulating across the run.
This is part of the API Testing Guide — start there for the full picture of reliable, deterministic tests.
Top comments (0)