Direct Answers
1. Fresh environment per test file? Yes for the global scope. Vitest runs each test file in a separate worker thread (default --pool=threads), giving each file its own globalThis. Mutating a global variable in one file does not affect another file's global scope.
2. Mechanism to reset global state between files? The primary cross-file leakage vector is module cache, not the environment instance. Within a worker, Node caches modules. Vitest provides vi.resetModules() to clear that cache for the current file, and vi.isolateModules()/vi.unisolateModules() for scoped isolation. For stronger guarantees, use --pool=forks (process-level isolation) or the Vitest 2.x environment: 'node:child_process' option. globalTeardown runs once per worker, not per file.
3. Vitest 1.x vs 2.x differences? 1.x uses Node's vm module to create a context per file but shares the module cache across the worker. 2.x (2024) defaults to stricter worker lifecycle management, adds node:child_process for process isolation, and changes default pool options. Projects upgrading should verify pool and poolOptions config.
How Isolation Actually Works
Worker Threads (Default)
- Each test file → dedicated worker thread
- Fresh
globalThis, process, built-ins per file
- Module cache shared within the worker across files
- Parallel execution → non-deterministic file order
Module Cache: The Real Leakage Path
// shared-module.js
export const state = { count: 0 };
// test-a.test.js
import { state } from './shared-module';
state.count = 42;
// test-b.test.js
import { state } from './shared-module';
console.log(state.count); // 42 if same worker, 0 if different worker
Because workers are reused across files, test-b may see test-a's mutation if they land in the same worker. This is not environment reuse—it's module cache persistence.
Reset Mechanisms
| Scope | Tool | Effect |
| Per test (within file) | vi.resetModules() in beforeEach | Clears module cache for that file's subsequent imports |
| Scoped block | vi.isolateModules(() => { ... }) | Runs imports in isolated cache, discards after |
| Worker lifetime | globalTeardown config | Runs once when worker shuts down; cleanup for worker-level state |
| Process isolation | --pool=forks or environment: 'node:child_process' (2.x) | Each file gets fresh process; no shared module cache |
Version-Specific Behavior
Vitest 1.x
environment: 'node' uses vm.createContext() per file
- Module cache shared across worker
- Default pool: threads
Vitest 2.x
- Default
environment: 'node:vm' (same vm approach, stricter defaults)
- New
environment: 'node:child_process' for process-per-file
- Improved worker teardown, changed
poolOptions defaults
- Upgrade note: check
pool config; 2.x may spawn more workers by default
Verification Steps
- Create two test files importing a shared mutable module
- Run
vitest run --reporter=verbose --pool=threads and observe - Repeat with
--pool=forks to confirm process isolation - Check version:
vitest --version
One Diagnostic Detail Needed
Are you seeing leakage in globalThis variables you set directly, or in imported module state? The fix differs: globalThis leaks indicate worker reuse misconfiguration; module leaks are expected without vi.resetModules() or process isolation.