A React component test suite passes file by file but fails when the whole suite runs, and the failures move around when you change the test order. Every test mounts through a shared helper that wraps components in a store and a cache provider. How do you track that down, and what is the structural fix?
answer
- green alone, red together
- order changes, failure moves
- module scope means once per process
- cleanup only touches the DOM
- new instance beats reset function
basics
~20 sOrder-dependent failures mean state is surviving between tests — typically a store or cache created once at module scope and shared by every mount. Confirm it by bisecting the run order, then build those instances inside the render helper so each test gets fresh providers.
solid answer
~50 sPass-alone-fail-together is the signature of leaked state, so treat it as a state problem, not a flaky-component problem. Confirm first: run the failing file alone, then with the file that precedes it, and re-run with the same order seed to prove the failure is reproducible rather than timing-based. Bisecting the file list narrows it to a pair fast. The usual culprit in a provider helper is a store or cache client constructed once at module scope. Every test then mounts against the same instance, so cached entries, dispatched actions and navigation history from test one are still present in test twenty. DOM cleanup does not touch any of it — it only unmounts and removes containers. The structural fix is to make freshness the default: construct those instances inside the helper, per render call, so isolation does not depend on anyone remembering a reset hook. Anything genuinely global that remains — module singletons, timers, storage — gets reset in shared setup, not per file.
go deeper
Recall that tests must not depend on each other, and that a suite passing alone but failing together points at leftover state rather than at a broken component.
Explain where the state lives — module-scope instances created once per process, storage, timers, unrestored mocks — and why DOM cleanup does not touch any of it.
Show the diagnosis: bisect the file order, reproduce with the same seed, read the failure text to identify which provider leaked, then move instance construction into the render helper rather than patching the failing file.
Own the policy that makes it impossible: randomised order in CI, global resets in shared setup, one sanctioned mounting path, and a stated rule that a test depending on another test's state is a defect even when it is green.
## Reading the symptom Three observations pin this down before you open any component: 1. **Passes in isolation.** The component and its assertions are fine. 2. **Fails in a full run.** Something outside the test is different when it runs with others. 3. **The failure moves when the order changes.** That rules out a genuine race in the component and points at accumulated state, because a timing bug would not follow the ordering so obediently. Order dependence is the tell. Flakiness that survives isolation is usually timing; flakiness that only appears in company is usually shared state. ## Confirming it cheaply - Run the suspect file alone. Green — good, hypothesis holds. - Run it immediately after one other file, walking a bisect over the file list. Two or three rounds usually finds the poisoning pair. - Re-run with the same order seed. Reproducible in the same order means state, not a race. - Read the failure text literally. "Found multiple elements" means DOM leaked. Stale data in an assertion means a cache leaked. A router landing on the wrong route means history leaked. Each points at a different provider. ## Where the state actually lives The helper is the prime suspect because it is the one thing every test runs. The classic defect: ```js // test-utils.jsx — the bug const client = makeClient() // built once, at import const store = makeStore() // shared by every test in the process export function renderWithProviders(ui) { return render(ui, { wrapper: ({ children }) => ( <Provider store={store}><QueryClientProvider client={client}>{children}</QueryClientProvider></Provider> )}) } ``` Module scope means "once per process". Every test in every file that imports this module shares one store and one cache. A test that logs a user in leaves them logged in. A test that populates a cache entry lets the next test render data it never fetched — and that one is nastier than a failure, because it can make a broken test pass. Other leak sites in the same family: - module-level singletons in app code (a configured client, an event bus, a cached config) that no test resets; - browser-ish globals: `localStorage`, `sessionStorage`, cookies, `document.documentElement` classes, `history` state; - fake timers left installed, or pending timers from an unmounted component; - mock implementations set in one file and not restored; - test data seeded into a shared in-memory backing store. DOM cleanup covers none of these. It unmounts trees and removes containers — that is its whole contract. ## The structural fix The weak fix is to add a reset hook to the failing file. It works today and rots immediately, because the next author writes a test without it. Make freshness structural instead: ```js // test-utils.jsx — fixed export function renderWithProviders(ui, { preloadedState, ...options } = {}) { const store = makeStore(preloadedState) // per call const client = makeClient() // per call function Wrapper({ children }) { /* providers with store + client */ } return { store, client, ...render(ui, { wrapper: Wrapper, ...options }) } } ``` Now isolation is a property of the only mounting path tests have, and forgetting is not an available mistake. Three supporting moves: - **Global resets live in shared setup**, applied to every file by configuration — clearing storage, restoring mocks, resetting singletons — rather than being copy-pasted per file. - **Randomise order in CI.** Deterministic order hides these bugs until the day someone adds a file. Randomised order surfaces them while the change that caused them is still fresh. - **Treat a passing test that depends on a previous test's data as a bug**, even though it is green. Silent false passes are the more expensive half of this failure mode. ## What not to do Do not "fix" it by forcing serial execution, pinning file order, or adding sleeps. Those hide the coupling and it comes back as a production bug — code that relies on a warm singleton usually behaves differently on a cold start in production, too. Also resist per-test resets of a shared instance as the permanent answer: a reset function has to be kept in sync with every piece of state the provider grows, and it will drift. Constructing a new instance cannot drift. ## The transferable principle Test independence must be guaranteed by the harness, not by discipline. Any state that outlives a test is a coupling channel, and the durable fix is always to make the fresh path the only path — same reasoning whichever framework, runner or provider library you are holding.
- Why is a leak that makes a test pass worse than one that makes it fail?A failure gets fixed. A false pass ships: the test asserts data that a previous test put in the cache, so the component's own fetching path is never exercised. It only surfaces when someone deletes or reorders the unrelated test, and by then the untested code has been in production for months.
- You add a resetStore() call in beforeEach and the suite goes green. Why is that not the fix you keep?It relies on every future test file remembering it, and the reset must be updated whenever the store grows state. Both drift. Constructing a fresh instance inside the render helper removes the option to forget and cannot fall out of sync, because there is nothing to keep in sync.
- How would you stop this class of bug from coming back after you fix it?Randomise test order in CI so coupling fails fast, put global resets in shared setup applied to every file, and make the provider helper the only sanctioned mounting path so no test can construct its own long-lived instances. Ideally review flags any module-scope mutable value in test utilities.
saying these in an interview costs you the question
- Blames flakiness and adds retries
- Pins the file order so the suite goes green
- Adds sleeps or waits to a state-leak failure
- Thinks DOM cleanup also resets stores and caches
- Fixes it with a reset hook in the one failing file