Skip to main content

ADR-041: Vitest for Frontend Testing

Status

Implemented

Date

2025-01-16 (Retrospective)

Decision Makers

  • Frontend Team - Testing framework selection
  • QA Team - Testing strategy

Layer

Testing

  • ADR-034: Vite Build System

Supersedes

  • Jest (migrated to Vitest)

Depends On

  • ADR-034: Vite Build System

Context

Frontend testing requires a modern, fast framework:

  1. Speed: Tests should run quickly
  2. Vite Integration: Share Vite configuration
  3. ESM Native: Modern module support
  4. React Testing Library: Component testing
  5. Coverage: Code coverage reporting

Requirements:

  • Jest-compatible API (easy migration)
  • Hot module replacement for watch mode
  • TypeScript native support
  • Coverage target: 80%
  • React Testing Library integration

Decision

We adopt Vitest as the frontend testing framework:

Key Design Decisions

  1. Vitest: Vite-native testing framework
  2. Jest API: Compatible for easy migration
  3. happy-dom: Lightweight DOM environment
  4. React Testing Library: Component testing
  5. c8 Coverage: V8-based coverage

Configuration

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'happy-dom',
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules/', 'src/test/'],
thresholds: {
lines: 80,
branches: 80,
functions: 80,
statements: 80,
},
},
},
});

Test Pattern

// Component test
import { render, screen, userEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { RequirementForm } from './RequirementForm';

describe('RequirementForm', () => {
it('validates required fields', async () => {
const onSubmit = vi.fn();
render(<RequirementForm onSubmit={onSubmit} />);

await userEvent.click(screen.getByRole('button', { name: 'Submit' }));

expect(screen.getByText('Title is required')).toBeInTheDocument();
expect(onSubmit).not.toHaveBeenCalled();
});

it('submits valid form data', async () => {
const onSubmit = vi.fn();
render(<RequirementForm onSubmit={onSubmit} />);

await userEvent.type(screen.getByLabelText('Title'), 'New Requirement');
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));

expect(onSubmit).toHaveBeenCalledWith(
expect.objectContaining({ title: 'New Requirement' })
);
});
});

Test Scripts

{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui"
}
}

Consequences

Positive

  • Speed: 10x faster than Jest
  • Vite Parity: Same config for build and test
  • HMR Tests: Instant test re-runs
  • ESM Native: No transpilation needed
  • Jest Compatible: Easy migration path

Negative

  • Newer Ecosystem: Fewer plugins than Jest
  • API Differences: Some Jest APIs not available
  • Browser Testing: Requires @vitest/browser
  • Learning Curve: New tool for team

Neutral

  • Coverage Tools: c8 vs Istanbul
  • Snapshot Testing: Supported but different format

Implementation Status

  • Core implementation complete
  • Tests written and passing
  • Documentation updated
  • Migration/upgrade path defined
  • Monitoring/observability in place

Implementation Details

  • Config: frontend/vitest.config.ts
  • Setup: frontend/src/test/setup.ts
  • Mocks: frontend/src/test/mocks/
  • Utils: frontend/src/test/utils/

LLM Council Review

Review Date: 2025-01-16 Confidence Level: High (100%) Verdict: STRONG APPROVAL

Quality Metrics

  • Consensus Strength Score (CSS): 0.95
  • Deliberation Depth Index (DDI): 0.85

Council Feedback Summary

Unanimous consensus to approve. Vitest is the industry-standard choice for Vite-based applications. The 10x speed improvement and shared config eliminate double-compilation costs.

Key Concerns Identified:

  1. Test Isolation: Vitest uses worker threads (not processes like Jest); global state can leak
  2. Snapshot Churn: happy-dom renders HTML differently; existing snapshots will fail on format
  3. Chart Testing: happy-dom lacks some SVG/Canvas implementations for visualization libraries

Required Modifications:

  1. Add Strict Cleanup:
    test: {
    restoreMocks: true,
    clearMocks: true,
    mockReset: true,
    pool: 'threads',
    isolate: true,
    }
  2. Plan Snapshot Migration: Expect one-time mass update (vitest -u)
  3. JSDOM Fallback: For chart tests, use // @vitest-environment jsdom
  4. Setup File: Add setupFiles: ['./src/test/setup.ts'] for global cleanup
  5. Coverage Exclusions: Exclude mock files and type definitions

Modifications Applied

  1. Documented strict cleanup configuration
  2. Added snapshot migration plan
  3. Documented jsdom fallback for visualizations
  4. Added setup file requirement

Council Ranking

  • All models reached strong consensus
  • gpt-5.2: Best Response (isolation)
  • gemini-3-pro: Strong (migration)

References


ADR-041 | Testing Layer | Implemented