Skip to main content

ADR-019: Migrate from Jest to Vitest for Testing

Statusโ€‹

Accepted

Dateโ€‹

2024-12-16

Contextโ€‹

Our test suite has 2,585 tests across 96 test files. We're experiencing significant issues with Jest's ESM mocking capabilities:

Current Problemsโ€‹

  1. 59 test failures in CI - Tests timeout in GitHub Actions that pass locally
  2. jest.unstable_mockModule() limitations - The ESM mocking API is experimental and unreliable:
    • Requires async top-level imports (await import() after mock setup)
    • Mocks don't hoist like CommonJS jest.mock()
    • TypeScript type inference struggles with mocked types
    • Still marked "unstable" even in Jest 30
  3. Slow test execution - ESM transformation overhead adds ~2-3x execution time
  4. Complex workarounds - Tests need explicit --experimental-vm-modules flag and special patterns

Failing Test Files (All Timeout Issues)โ€‹

FileDurationFailures
deployment-guidance-tool.test.ts210s12 tests
troubleshoot-guided-workflow-tool.test.ts377s15 tests
review-existing-adrs-tool.test.ts120s10 tests
adr-bootstrap-validation-tool.test.ts241s22 tests

Alternatives Consideredโ€‹

  1. Stay with Jest + Increase Timeouts: Doesn't solve the underlying ESM mocking issues
  2. Jest + Sinon: Better mocking but doesn't solve ESM transformation overhead
  3. Jest + Dependency Injection: High refactoring effort, same speed issues
  4. Vitest: Native ESM support, faster execution, Jest-compatible API

Decisionโ€‹

Migrate from Jest to Vitest for all testing.

Why Vitestโ€‹

  1. Native ESM Support: No experimental flags, no transformation overhead
  2. 3-5x Faster: Leverages Vite's optimized module handling
  3. 95% Jest Compatible: Same describe/it/expect syntax, similar mocking API
  4. Better TypeScript: Native support without Babel
  5. Simpler Mocking: vi.mock() works like jest.mock() should have worked

Migration Syntax Changesโ€‹

// Before (Jest ESM)
import { jest } from '@jest/globals';
jest.unstable_mockModule('../../src/utils/adr-discovery.js', () => ({
discoverAdrsInDirectory: jest.fn(),
}));
const { generateDeploymentGuidance } = await import('../../src/tools/...');

// After (Vitest)
import { vi, describe, it, expect } from 'vitest';
vi.mock('../../src/utils/adr-discovery.js', () => ({
discoverAdrsInDirectory: vi.fn(),
}));
import { generateDeploymentGuidance } from '../../src/tools/...'; // Normal import!

Migration Planโ€‹

Phase 1: Setup (This PR)โ€‹

  • Install Vitest and dependencies
  • Create vitest.config.ts
  • Update package.json scripts
  • Migrate one test file as proof-of-concept

Phase 2: Core Migrationโ€‹

  • Migrate failing test files first (4 files, ~59 tests)
  • Migrate tool tests (tests/tools/)
  • Migrate utility tests (tests/utils/)

Phase 3: Complete Migrationโ€‹

  • Migrate integration tests
  • Migrate performance tests
  • Remove Jest dependencies
  • Update CI workflows

Phase 4: Cleanupโ€‹

  • Remove Jest configuration
  • Update documentation
  • Archive migration scripts

Consequencesโ€‹

Positiveโ€‹

  • Faster test execution (3-5x improvement expected)
  • Reliable ESM mocking without experimental flags
  • Simpler test setup code
  • Better TypeScript integration
  • More active development and community support

Negativeโ€‹

  • One-time migration effort (~2-4 hours)
  • Team needs to learn minor API differences
  • Some Jest-specific features may need alternatives

Neutralโ€‹

  • Test files need syntax updates (mostly search-replace)
  • CI configuration needs updates
  • Coverage reporting tools remain compatible

Referencesโ€‹