Vitest Configuration & Setup

Establishing deterministic test execution requires explicit configuration boundaries. This guide details production-grade Vitest setup for modern JavaScript architectures, focusing on environment isolation, mock interception, and CI pipeline enforcement.

Core Configuration Architecture & Dependency Resolution

Establishing a robust Component & Integration Testing Frameworks baseline requires explicit environment targeting. Configure vitest.config.ts to isolate unit and integration scopes, preventing cross-contamination of mock registries.

Implementation Patterns

  1. Initialize vitest.config.ts using defineConfig for type safety and explicit environment routing.
  2. Set test.environment to jsdom for DOM-heavy suites or node for pure logic. Avoid implicit defaults.
  3. Configure resolve.alias to bypass Vite bundling quirks and map internal paths directly to source directories.
  4. Restrict globals: true exclusively to legacy migration paths. Modern codebases should import vi, describe, and expect explicitly to prevent namespace pollution.
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
 resolve: {
 alias: {
 '@': path.resolve(__dirname, './src'),
 '~': path.resolve(__dirname, './lib'),
 },
 },
 test: {
 environment: 'jsdom',
 globals: false, // Enforce explicit imports for deterministic execution
 include: ['src/**/*.test.{ts,tsx}', 'src/**/*.spec.{ts,tsx}'],
 exclude: ['node_modules', 'dist', '.next', 'coverage'],
 // CI-specific overrides handled via environment variables or CLI flags
 cache: process.env.CI ? false : { dir: 'node_modules/.vite' },
 },
});

CI Pipeline Rules

  • Enforce strict test.include glob patterns to prevent accidental execution of utility files or configuration scripts.
  • Set test.cache: false in CI to guarantee deterministic runs and eliminate stale module resolution artifacts.

Debugging Workflow

  • Run vitest --inspect-brk with a Node debugger attached for synchronous configuration evaluation and breakpoint mapping.
  • Use VITE_DEBUG=1 to trace module resolution failures and dependency graph construction during startup.

Framework-Specific Integration & Mocking Strategies

Aligning Vitest with modern UI libraries demands strict adherence to Testing Library Best Practices. When wiring framework adapters, prioritize explicit render wrappers over implicit globals. For server-side routing contexts, refer to Configuring Vitest for Next.js App Router to handle use client directives and server component boundaries.

Implementation Patterns

  1. Implement setupFilesAfterEnv to inject global test utilities, custom matchers, and cleanup routines.
  2. Use vi.mock with factory functions for dynamic module interception. Avoid hoisting static mocks that bypass runtime evaluation.
  3. Configure deps.inline for ESM-only packages that fail under Vite’s CommonJS transformation pipeline.
// vitest.setup.ts
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

// Enforce DOM cleanup after every test to prevent state leakage
afterEach(() => cleanup());

// vi.mock example with dynamic factory
vi.mock('@/services/api', async (importOriginal) => {
 const actual = await importOriginal<typeof import('@/services/api')>();
 return {
 ...actual,
 fetchUser: vi.fn().mockResolvedValue({ id: 'test-1', name: 'Mock User' }),
 };
});
// vitest.config.ts (extension)
export default defineConfig({
 test: {
 setupFilesAfterEnv: ['./vitest.setup.ts'],
 deps: {
 inline: ['some-esm-only-package'], // Force Vite to process as ESM
 },
 },
});

CI Pipeline Rules

  • Block PR merges if coverage.thresholds fall below 80% for critical paths. Configure thresholds per directory to enforce architectural boundaries.
  • Cache node_modules/.vite across pipeline runs to reduce cold start latency without compromising test determinism.

Debugging Workflow

  • Leverage vitest --reporter=verbose to isolate failing component trees and trace assertion failures to specific render cycles.
  • Apply console.trace() within vi.spyOn callbacks to map invocation chains and verify dependency injection order.

CI Enforcement, Parallelization & Reliability Tradeoffs

Scaling execution across distributed runners introduces tradeoffs between isolation overhead and throughput. While Vitest excels at rapid feedback loops, complex DOM-heavy suites may require offloading to Playwright Component Testing for realistic browser rendering guarantees. Implement shard-aware coverage aggregation to maintain accurate metrics across parallelized jobs.

Implementation Patterns

  1. Enable pool: 'forks' for CPU-bound test suites to bypass thread pool limitations and leverage full core utilization.
  2. Implement test.sequential for stateful integration flows that require strict execution ordering to prevent race conditions.
  3. Use test.retry: 2 sparingly for known network-dependent mocks. Do not apply globally; it masks underlying instability.
// vitest.config.ts (production CI profile)
export default defineConfig({
 test: {
 pool: 'forks',
 poolOptions: {
 forks: {
 singleFork: false,
 execArgv: ['--max-old-space-size=4096'],
 },
 },
 sequence: {
 shuffle: true, // Enforce non-deterministic ordering locally to catch hidden dependencies
 },
 },
});

// Example of sequential execution for stateful tests
import { describe, it, expect } from 'vitest';

describe('Stateful Integration Flow', () => {
 it.sequential('initializes database connection', async () => { /* ... */ });
 it.sequential('executes migration batch', async () => { /* ... */ });
 it.sequential('verifies schema integrity', async () => { /* ... */ });
});

CI Pipeline Rules

  • Enforce --maxWorkers=auto with explicit memory caps (--max-old-space-size=4096) to prevent OOM kills during parallel execution.
  • Fail builds on test.watch artifacts or leftover __snapshots__ drift. Run vitest --run --update only in controlled release branches.

Debugging Workflow

  • Capture vitest --run --reporter=json output for CI log parsing and automated failure triage.
  • Profile test execution with --bail=1 to halt on first regression and isolate the exact failure vector before cascading timeouts occur.

Production CI Configuration

# .github/workflows/vitest.yml
name: Vitest CI Enforcement
on:
 pull_request:
 branches: [main, develop]

jobs:
 vitest:
 runs-on: ubuntu-latest
 strategy:
 matrix:
 shard: [1, 2, 3]
 steps:
 - uses: actions/checkout@v4
 - uses: actions/setup-node@v4
 with:
 node-version: 20
 cache: 'npm'
 - run: npm ci
 - name: Run Tests with Coverage
 run: npx vitest --run --coverage --shard=${{ matrix.shard }}/3
 env:
 CI: true
 VITE_DEBUG: 1
 - name: Enforce Coverage Thresholds
 run: npx vitest --run --coverage --coverage.thresholds.lines=80 --coverage.thresholds.branches=80
 - name: Upload Coverage Artifacts
 uses: actions/upload-artifact@v4
 with:
 name: coverage-${{ matrix.shard }}
 path: coverage/