vitest

SKILL.md

Vitest Best Practices

For test philosophy (behavior vs implementation, TDD workflow, when to mock), see /testing-philosophy.

Critical Rules

  1. Node 22+: Use pool: 'forks' - threads have known issues
  2. CI optimization: Single worker, disable watch, enable isolate: false if safe
  3. Coverage: Always define coverage.include - defaults exclude too much
  4. Mocking: Prefer vi.spyOn over vi.mock - avoids hoisting footguns
  5. 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

Weekly Installs
19
GitHub Stars
5
First Seen
Feb 5, 2026
Installed on
gemini-cli19
opencode19
codebuddy19
github-copilot19
codex19
kimi-cli19