In an MSW-based suite, one test calls server.use() to make GET /api/orders return an empty list, and a later test that expects the normal payload starts failing. Why does the override leak, and what is the standard fix?
answer
- passes alone, fails in the suite
- two layers of handlers, not one
- the newer layer wins
- nothing clears it for you
- afterEach restores the baseline
basics
~20 sserver.use() adds a runtime handler that takes precedence over the initial ones and stays for the rest of the process. Call server.resetHandlers() in afterEach so every test starts from the shared baseline, or register the override as a one-time handler.
solid answer
~50 s`server.use()` does not replace the handler you started with — it prepends a runtime handler that wins over the initial list, and it lives until something removes it. Nothing removes it automatically, so the next test inherits the empty-list response and fails on a payload it never asked to change. The fix is the standard lifecycle: `server.resetHandlers()` in an `afterEach`, which drops every runtime handler and restores the array MSW was set up with, alongside `server.listen()` in `beforeAll` and `server.close()` in `afterAll`. If a single test needs the override only for the first matching request, register it with the `{ once: true }` option so it retires itself after use. The general rule is that the shared handlers describe the happy path and each test declares only its own deviation — which only works if the deviation is reliably undone.
code
javascript · 9 linesimport { http, HttpResponse } from 'msw'
import { server } from './test-setup'
test('renders the empty state when there are no orders', async () => {
server.use(
http.get('/api/orders', () => HttpResponse.json([]), { once: true })
)
// render the screen and assert on the empty state
})go deeper
Remember the three lifecycle hooks — listen before all, resetHandlers after each, close after all — and that server.use() is a per-test override that only stays scoped because of that reset.
Explain the precedence rule that runtime handlers are checked before the initial ones, why they persist across tests, and what { once: true } changes about when a handler retires.
Recognise 'passes alone, fails in the suite' as shared-state order dependence and be able to walk the debugging path, plus argue for a baseline-plus-deviation fixture layout that keeps overrides small and reversible.
Own test isolation as a property of the whole suite: which state is process-wide, what guarantees the runner gives about ordering and parallelism, and how you stop order-dependent flakes from becoming an accepted background cost.
## Two layers of handlers An MSW setup has an initial handler list — the array passed to `setupServer(...handlers)` — and a runtime layer added later by `server.use()`. They are not the same thing, and the difference is exactly what this bug is made of. Runtime handlers are prepended, so they are considered before the initial ones. That is what makes an override work without needing to find and delete the original handler: you add a more recent handler for the same method and path, it matches first, and the baseline handler is simply never reached. ```js test('shows the empty state', async () => { server.use( http.get('/api/orders', () => HttpResponse.json([])) ) // render, then assert the empty state }) ``` ## Why it leaks The runtime layer persists for the lifetime of the server object, which in a typical suite means the lifetime of the whole test process — the same `server` instance is created once in a setup file and shared by every test file that runs in it. MSW deliberately does not clear overrides per test, because it has no idea where your test boundaries are; that is your runner's job to tell it. So the failure looks like this: test A overrides `/api/orders` to return `[]`, test B renders the same screen expecting three orders, and gets zero. The tell is order dependence — test B passes when run alone with `.only`, and fails in the full suite. Any time a test passes in isolation and fails in a suite, shared mutable state between tests is the first hypothesis, and MSW's runtime handler list is one of the usual holders of it. ## The lifecycle that fixes it ```js beforeAll(() => server.listen()) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) ``` `resetHandlers()` with no arguments removes every runtime handler and restores the initial list exactly as it was at setup time. Put it in `afterEach`, not `beforeEach` — the difference matters if a test throws, because `afterEach` still runs and the next test still starts clean. `close()` stops interception so it does not bleed into anything else sharing the process. There is also `server.resetHandlers(...nextHandlers)`, which replaces the baseline itself rather than restoring it. That is occasionally useful for a whole file that wants a different default, but it is a sharp tool: it changes what "reset" means for everything that follows in that server instance, so prefer scoping it in a `beforeAll` inside a `describe` and understand you have changed the baseline, not layered on it. ## One-time handlers When the point of the override is *the next request specifically* — for example the first attempt returns one payload and the component then refetches — register it as one-time: ```js server.use( http.get('/api/orders', () => HttpResponse.json([]), { once: true }) ) ``` A one-time handler is removed after it has resolved a matching request, so the second request falls through to the baseline handler. This lets you express a sequence of responses without stateful bookkeeping in the resolver, and it limits the blast radius even if a reset is missing. It is not a substitute for `resetHandlers()`, though: if the test never triggers the request — because an assertion failed earlier, or the component short-circuited — the one-time handler is still sitting there for the next test. ## The discipline underneath The structural principle generalises beyond MSW: shared fixtures describe the *default* world, and a test declares only its deviation from it, with an automatic mechanism restoring the default afterwards. A suite that instead re-declares every endpoint in every test is verbose and drifts endpoint by endpoint; a suite that mutates shared state with no restore is order-dependent and produces the worst class of flake — the one that reproduces only in CI, only in a particular file order, and disappears the moment you try to debug a single test. A related habit worth naming: keep overrides at the top of the test, before rendering. An override registered after the component has already fired its request changes nothing about that in-flight request, and the resulting confusion looks like MSW ignoring the handler. ## What to say in an interview Name the precedence rule (runtime handlers win), name the persistence (they survive until reset), name the fix (`resetHandlers()` in `afterEach`, `listen`/`close` around the suite), and mention `{ once: true }` for sequenced responses. Then connect it to the general symptom — passes alone, fails in the suite — because that is the reasoning the question is really probing.
- Why put resetHandlers in afterEach rather than beforeEach?Both usually work, but `afterEach` still runs when a test throws, so a failing test cannot leave its override behind for the next one. `beforeEach` also leaves the last test's override live for anything that runs after the file, such as global teardown. Cleaning up where you made the mess is the more robust convention.
- How would you make an endpoint return a different response on the first call than on the second?Register a one-time override with `{ once: true }` for the first response and let the baseline handler answer the retry, or keep a counter in a resolver closure and branch on it. Prefer the one-time handler: it is declarative, and the counter approach adds its own piece of state you then have to reset between tests.
- A test overrides a handler but the component still sees the original response. What is the likely cause?The override was registered after the request had already been made — typically `server.use()` placed below the render call, or inside a `waitFor`. Interception is decided when the request is issued, so an override must be in place first. The other common cause is a mismatched predicate: a different method, or a relative path where the app calls an absolute cross-origin URL.
saying these in an interview costs you the question
- Thinks server.use() replaces the initial handler permanently
- Says MSW clears overrides between tests automatically
- Calls resetHandlers only in the file that added the override
- Registers the override after rendering the component
- Treats { once: true } as a substitute for resetting