skip to content

Your mount helper builds a fresh provider per test, yet a value one test wrote is still visible in the next. Where is the leak?

level: seniorimportance: should knowfreq 44%

answer

  1. the provider is new, the value is not
  2. module scope, not mount scope
  3. module loads are cached per file
  4. pass alone, fail in suite
  5. factory per test beats enumerated reset

basics

~10 s

In state created once when a module first loads — a store, cache or client built at module scope. The provider is new each test, but it hands the tree the same long-lived instance.

solid answer

~50 s

A provider is just a wrapper; what leaks is the thing it provides. If a store, data cache or client is constructed at module scope — created as the module is first evaluated and exported — then it is built once per module load, not once per test, and module loads are cached for the lifetime of the test file or worker. So every test gets the same container, complete with the previous test's writes, subscribers and cached entries, while the provider around it is genuinely new. Two fixes exist: make the container a factory the helper calls per test, so each mount owns its own, or keep the singleton and reset it in setup — restore initial state, clear cached entries, drop subscriptions. The factory is stronger, because a reset only clears the fields somebody remembered to enumerate, and every field added later is a silent regression.

go deeper

for a junior

Remember the distinction: a new provider each test does not mean new state. A store created when its module loads is shared by every test in the file.

for a middle

Explain module-scope lifetime and cached module loads, and describe both fixes — a factory called per mount, or a reset before each test.

for a senior

Show a diagnosis path: reproduce with fixed order, bisect the coupled pair, follow provided values to their construction site, then fix the lifetime rather than the ordering.

for a principal

Treat it as a design constraint: direct imports of a singleton block per-test instances. Decide whether the codebase routes access through providers and how that is enforced.

## Provider freshness is not state freshness It is easy to conflate two different things. A **provider** is a component that makes a value reachable to the subtree beneath it. The **value** is a container with its own lifetime. Rebuilding the provider on every mount says nothing about where the value came from. If the value is created when its defining module is first evaluated and exported as a ready-made instance, then its lifetime is the module's — and a test runner loads a module once per file or per worker process and caches it. Every test in that file receives the same instance. The result is a suite that fails in ways that feel supernatural: - tests pass one at a time and fail when the file runs; - failures move when the order changes or when a case is skipped; - the first test in a file always passes and a later one sees a list with too many items; - a test that never signed anyone in finds a session already present. ## What tends to live at module scope | Container | What it accumulates | Why a reset is easy to get wrong | |---|---|---| | An external state store | Written values, and the subscribers each mount attached | Restoring one slice leaves siblings dirty; subscribers keep firing | | A data cache / query client | Cached entries, in-flight requests, retry timers | Clearing entries may leave pending work that resolves into the next test | | A router with a real history | Pushed entries, so the back stack grows | Resetting the current entry does not shorten the stack | | A configured request client | Base settings and interceptors added per test | Interceptors stack silently and apply to later cases | | A cross-cutting singleton — logger, analytics, feature flags | Recorded calls, overridden flags | Assertions on call counts start at whatever the previous test left | The common shape: the container was designed to live as long as the application, and a test file is a second application that starts many times. ## Finding it 1. **Reproduce deterministically.** Run the file with a fixed order, then bisect: run the suspect test after each other test until the pair that couples is isolated. 2. **Read what the helper hands down, not what it builds.** Follow each provided value to its construction site. A value imported ready-made rather than constructed inside the helper is the suspect. 3. **Prove it.** Log or assert the container's identity at the start of two tests. Same identity across tests, with a new provider each time, is the diagnosis. 4. **Check the quiet ones.** A store is obvious; interceptors, flag overrides and analytics recorders are the ones nobody thinks of as state. ## The two fixes, and why one is better **Factory per test.** Export a function that builds the container, have the helper call it on every mount, and let the provider hand the new instance down. Nothing needs enumerating: a field added next year is initialised because construction is what initialises it. The cost is that the container must be injectable — components reach it through the provider rather than importing the instance directly — which is a design change if the codebase imports it everywhere. **Reset in setup.** Keep the singleton and, before each test, restore its initial state, clear cached entries, cancel pending work and detach subscribers. Cheaper to adopt in a codebase that imports the instance directly, and the standard interim step. Its weakness is completeness: the reset enumerates fields, so every field added later is a leak nobody wrote a test for. If you go this way, make the reset live next to the container's definition so the two are edited together, and prefer "rebuild internal state from the initial shape" over clearing keys one by one. A reset also cannot undo work already in flight. A pending request or a scheduled retry belongs to the container; a reset that clears entries but leaves the timer running lets the resolution land during a later test. ## Say this in an interview Name the mechanism precisely: module-level construction gives the container the module's lifetime, and the runner caches module loads per file or worker, so "once per test" is not what happens. Then give both fixes with the tradeoff, and mention that direct imports of a singleton are what make the factory hard — which is why the leak is a design signal, not only a test bug. Note also that the same leak may not reproduce across whole runs: if the runner isolates each file in its own worker, the coupling is invisible between files and appears only within one.

  • Why is a per-test factory usually stronger than a reset function on the singleton?
    A reset clears the fields somebody listed, so every field added later leaks silently until a test happens to notice. Construction initialises everything by definition. A reset also cannot cancel work already scheduled inside the container. The reset's advantage is adoption cost: it works without changing components that import the instance directly.
  • The coupling never reproduces across the whole run, only when one file runs alone. Why?
    Runners commonly isolate files in separate worker processes with their own module caches, so a module-scope container is shared within a file and not between files. Within-file order then decides the outcome. It also means the bug can hide when parallelism changes, so reproduce with a fixed order in a single worker while diagnosing.
  • Besides stores and caches, what module-scope state most often leaks unnoticed?
    Things not thought of as state: request-client interceptors added by a test and never removed, feature-flag overrides, a recorded call list on an analytics or logging double, and a running timer or retry inside a data client. Each accumulates across cases and quietly changes later tests' starting conditions.
  • What makes a codebase resist the factory fix?
    Components that import the container instance directly instead of reading it from a provider. The instance is then reachable without the tree, so a per-test instance cannot be substituted. Routing every access through the provider is the enabling change, and it also makes the dependency visible at the mount site.

Repainting the doorway does not empty the room behind it. The provider is the doorway; the store is the room, and it still holds what the last visitor left.

saying these in an interview costs you the question

  • Says a fresh provider per test guarantees fresh state
  • Blames the framework's scheduler for order-dependent failures
  • Enumerates a few store fields in a reset and calls the leak closed
  • Fixes the symptom by forcing a test order or moving a case to its own file
  • Forgets pending requests and timers inside a container that a state reset leaves running
  • Assumes tests are isolated because each file runs in its own worker