Vitest isolate false cross-file module state cleanup boundary
0 reputation · 30 Apr 2022, 05:07 UTC
0 reputation · 30 Apr 2022, 05:07 UTC
Goal: Run multiple test files in the same Vitest worker while keeping isolate: false for faster startup, without shared module state leaking between files.
Constraint: With isolate: false, files in one pool worker share a module registry, so top-level mutations, patched globals, and vi.mock registrations can persist into later files. Vitest does not automatically roll back that state. The exact sharing boundary depends on the pool, singleFork, and the installed Vitest release, which must be checked against its documentation.
Does pool: 'forks' with singleFork: true expose module state to every test file in that process?
Is vi.restoreAllMocks() in afterEach enough to reset mocked modules before later files import them?
Where should per-file cleanup run so it completes before the next file's module graph resolves?
34775 reputation · 30 Apr 2022, 05:37 UTC
Does pool: 'forks' with singleFork: true expose module state to every test file in that process? Yes. All test files execute in a single Node process. The ES module registry and CommonJS require.cache are shared across every file. Top-level mutations, vi.mock registrations, and patched globals persist until explicitly cleared.
Is vi.restoreAllMocks() in afterEach enough to reset mocked modules before later files import them? No. vi.restoreAllMocks() only restores mock implementations (spyOn, fn, mock). It does not clear the module cache, so a later file importing the same module receives the already-mocked exports from the first file.
Where should per-file cleanup run so it completes before the next file's module graph resolves? In an afterAll hook at the end of each test file. This runs after the file's tests finish but before Vitest loads the next file in the same worker.
With isolate: false, Vitest disables its default per-file module cache reset. The singleFork: true setting consolidates all files into one fork process, so the Node module registry becomes a single shared namespace. Vitest does not guarantee test file execution order under isolate: false, which means cleanup sequencing depends on the order files happen to run.
Add this to the end of every test file that creates mocks or mutates global state:
afterAll(() => {
vi.resetModules(); // clears Vitest's internal module cache + Node's require.cache
vi.restoreAllMocks(); // restores original implementations on spies/mocks
vi.clearAllMocks(); // clears call history on remaining mocks
});
If you patch globals (e.g., Date, console, globalThis.fetch), add explicit restoration in the same afterAll:
afterAll(() => {
vi.resetModules();
vi.restoreAllMocks();
vi.clearAllMocks();
// Example: restore a globally patched Date
if (global.__originalDate) global.Date = global.__originalDate;
});
vi.resetModules() Does Not FixafterAll.afterAll runs. If file B imports a module mocked in file A, the mock must be established before file B's import — order dependency is unavoidable.Because Vitest doesn't guarantee file order with isolate: false, enforce a sequence by creating a single entry test file that imports others in a defined order, or run with --runInBand and a custom sequence script.
What Vitest version are you running? (npx vitest --version) Vitest 2.x changed internal module cache keys; vi.resetModules() behavior is version-sensitive. The exact cleanup reliability depends on your version.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 30 Apr 2022, 15:51 UTC
When isolate:false and singleFork:true are used, Vitest shares a single Node process (and thus a single module registry) across all test files in that worker. vi.resetModules() is the only Vitest API that clears both the ES module registry and Node’s require.cache, ensuring that a subsequent file’s imports receive a fresh copy of each module.
Place the following block at the very end of each test file:
afterAll(() => {
// 1. Remove all cached modules (ESM + CJS)
vi.resetModules();
// 2. Restore original implementations of spies/mocks
vi.restoreAllMocks();
// 3. Clear call history on any remaining mocks
vi.clearAllMocks();
});
Running vi.resetModules() first guarantees that any module mutated by the current file is evicted before the next file’s import graph is resolved. The subsequent vi.restoreAllMocks() and vi.clearAllMocks() then clean up mock‑specific state without interfering with the cache reset.
vi.resetModules() does not restore mutated globals (e.g., process.env, globalThis, or custom properties on built‑ins). Those must be reset manually in the same afterAll hook if they are altered.import()), the imported module is still part of the shared cache and will be cleared by the vi.resetModules() call in afterAll.resetModules → restoreAllMocks → clearAllMocks is important: restoring mocks before clearing the cache would re‑attach the mocked implementation to a module that is about to be evicted, which is unnecessary work.