Skip to content

vi.fn "memory leak" with isolate: false #9492

Description

@andrewbrennanfr

Clear and concise description of the problem

In our project, we have a need to use isolate: false and fileParallelism: false in vitest.

We have seen that under this configuration, the number of vitest mocks being kept in memory grows as your test suite runs. This is not surprising given the shared context.

What is surprising, is there is no clear mechanism to control this.

The result is that calling functions like clearAllMocks or restoreAllMocks get progressively slower over time (as we simply loop over all active mocks) - without ever having a way to reduce the size of the set.

Here is a reproduction 👉 https://stackblitz.com/edit/vitest-dev-vitest-y6nkb7na?file=vite.config.ts,test%2Ftest01.test.ts,vitest.setup.ts,test%2Ftest05.test.ts,test%2Ftest04.test.ts&initialPath=__vitest__/

I have 5 test files, that each create a large number of mocks and you can see the performance of clearAllMocks degrades over time - relative to the number of mocks being created.
This is an extreme case (with 1000's of mocks created) - but in our real-world case, we aren't mocking many functions - but having a lot of tests (5000+ tests) & even mocking a reasonable number of functions per test, get's out of control quickly.

Using isolate: true, does solve this problem - because the context is reset between tests. But this introduces other problems we then have to overcome.

Suggested solution

I find it surprising there is no way to "clean/kill mocks" that are in memory.
We have the power to clean up other parts of shared context unstubAllGlobals or unstubAllEnvs are maybe good examples?
I'd kind of expect some killAllMocks function.

Alternative

I guess there is an argument that we should mock less things. Or not run with isolate: false.
But if we imagine for a second, that there is a use case for mocking a large number of functions in a shared context (we have one in our project) - there is not equal alternative.

Alternatively I guess we could also say that resetAllMocks or restoreAllMocks should be keeping this list under control itself. I don't see any reason that we would want to just let this set grow forever as my tests run.

Additional context

I'd be open to a suggestion on how to solve this problem with existing methods - without resorting to:

  • Changing isolate:false or fileParallelism: false
  • Mocking less things

Validations

Activity

  1. MikeDabrowski commented on Aug 26, 2026

    @MikeDabrowski

    We're seeing what looks like the same underlying pattern, though triggered by test fixtures rather than vi.fn mocks specifically — worth noting in case it points to a broader "nothing gets released under isolate: false" issue rather than something mock-registry-specific.

    Our setup: Angular + Vitest, ~8500 tests across ~970 files, pool: 'threads'/'vmThreads', isolate: false, restoreMocks: true, mockReset: true (for the same reason — perf, at this test count isolate: true is too slow to be usable in CI).

    Running with logHeapUsage: true, process.memoryUsage().heapUsed climbs steadily for the whole run rather than staying flat between files:

    sample 0:    94 MB
    sample 500:  238 MB
    sample 1000: 497 MB
    sample 1500: 879 MB
    sample 2000: 1144 MB
    sample 2500: 1250 MB
    sample 3000: 1453 MB
    sample 3500: 1691 MB
    

    Of ~3580 consecutive test-to-test deltas, 2259 were increases vs. 783 decreases — so growth dominates, but it's not that GC never runs: the 10 largest single-step drops (600–1200 MB each) all cluster very late in the run, once heap pressure is already high, suggesting GC is deferred rather than truly blocked. That's consistent with some memory (real garbage) eventually getting reclaimed while another category (retained by Vitest itself, e.g. the mock registry this issue describes, but possibly also fixtures/DI graphs) never does.

    Switching to isolate: true does stop the growth for us too, but became impractical at this scale — a partial run at ~1/5 the speed of isolate: false before we gave up.

    Given clearAllMocks/restoreAllMocks/resetAllMocks are already the documented way to reset state between tests, it'd be a meaningful win if they (or a new explicit API, as suggested above) could also drop the retained references so long suites don't have to choose between speed (isolate: false) and bounded memory.

  2. sdjayna commented on Sep 13, 2026

    @sdjayna

    A data point on why this matters beyond memory, and a cross-reference. The registry walk that #11092 removes got about twice as slow per step on Node 26.4.0 and later, so suites running with isolate: false and clearMocks: true on Node 26 doubled in wall time compared with Node 24 (one 174-file Angular suite: about 7 minutes to about 20 on CI). The cause is a V8 cache collision that vi.fn() triggers exactly: Object.defineProperty(mock, "length", ...) normalizes the function, the state object literal with its lastCall getter is normalized too, and on Node 26.4+ the two evict each other from V8's NormalizedMapCache on every mock, so every store in mockClear misses.

    Bisected and reported upstream with a 16-line repro: nodejs/node#66011, V8 https://issues.chromium.org/issues/560730175, fix https://chromium-review.googlesource.com/c/v8/v8/+/8397811. For anyone on Vitest 4 who cannot upgrade yet, applying the 27-line change from #11092 to @vitest/spy 4.1.11 as a package patch takes the same suite from 8 minutes to 1 min 20 s on Node 26.8.

  3. locked and limited conversation to collaborators on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions