Vitest Best Practices
For test philosophy (behavior vs implementation, TDD workflow, when to mock), see /testing-philosophy .
Critical Rules
-
Node 22+: Use pool: 'forks'
-
threads have known issues
-
CI optimization: Single worker, disable watch, enable isolate: false if safe
-
Coverage: Always define coverage.include
-
defaults exclude too much
-
Mocking: Prefer vi.spyOn over vi.mock
-
avoids hoisting footguns
-
RTL cleanup: Requires globals: true in config
Memory Safety (MANDATORY)
Vitest in watch mode holds ~2 GB per process. 12 repos = 24+ GB = machine crash.
Rule Why
"test" script MUST be vitest run , never bare vitest
Bare vitest = watch mode = persistent process
Hook/CI subprocesses: env={**os.environ, "CI": "true"}
Belt-and-suspenders against watch mode
Pool config: forks , maxForks: 4 on <=36 GB Caps memory at ~2 GB total
Never run vitest --watch from automated/agent contexts Zombies accumulate across sessions
Never delegate >3 parallel agents from hooks/scripts Each agent spawns its own Node processes
When onboarding a new repo, check package.json test script immediately.
Quick Reference
Pool Selection (Node 22+)
Pool Use When Avoid When
forks
Node 22+, default choice
threads
Node <22, CPU-bound tests Node 22+ (native fetch issues)
vmThreads
Need isolation + speed Memory-constrained CI
CI Configuration
export default defineConfig({ test: { pool: 'forks', poolOptions: { forks: { singleFork: true, // CI: predictable, less overhead }, }, isolate: false, // Faster if tests don't leak state reporters: ['verbose'], coverage: { reportOnFailure: true, }, }, })
Coverage Quick Reference
coverage: { provider: 'v8', // Accurate in Vitest 3.2+ include: ['src/**'], // ALWAYS define - defaults miss files reporter: ['text', 'lcov'], reportOnFailure: true, // Get coverage even on test failure }
Mocking Quick Reference
// PREFER: vi.spyOn - explicit, no hoisting issues const spy = vi.spyOn(service, 'method').mockReturnValue('mocked')
// AVOID unless necessary: vi.mock - hoisted, can't use imports vi.mock('./module', () => ({ fn: vi.fn() }))
Reference Files
-
Pool Configuration - threads vs forks vs vmThreads
-
Performance Patterns - CI optimization, sharding
-
Coverage Strategy - v8 vs Istanbul, thresholds
-
Mocking Pitfalls - vi.mock hoisting, cleanup