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โ
- 59 test failures in CI - Tests timeout in GitHub Actions that pass locally
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
- Requires async top-level imports (
- Slow test execution - ESM transformation overhead adds ~2-3x execution time
- Complex workarounds - Tests need explicit
--experimental-vm-modulesflag and special patterns
Failing Test Files (All Timeout Issues)โ
| File | Duration | Failures |
|---|---|---|
deployment-guidance-tool.test.ts | 210s | 12 tests |
troubleshoot-guided-workflow-tool.test.ts | 377s | 15 tests |
review-existing-adrs-tool.test.ts | 120s | 10 tests |
adr-bootstrap-validation-tool.test.ts | 241s | 22 tests |
Alternatives Consideredโ
- Stay with Jest + Increase Timeouts: Doesn't solve the underlying ESM mocking issues
- Jest + Sinon: Better mocking but doesn't solve ESM transformation overhead
- Jest + Dependency Injection: High refactoring effort, same speed issues
- Vitest: Native ESM support, faster execution, Jest-compatible API
Decisionโ
Migrate from Jest to Vitest for all testing.
Why Vitestโ
- Native ESM Support: No experimental flags, no transformation overhead
- 3-5x Faster: Leverages Vite's optimized module handling
- 95% Jest Compatible: Same
describe/it/expectsyntax, similar mocking API - Better TypeScript: Native support without Babel
- Simpler Mocking:
vi.mock()works likejest.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.jsonscripts - Migrate one test file as proof-of-concept
Phase 2: Core Migrationโ
- Migrate failing test files first (4 files, ~59 tests) โ deliberately left unchecked. See Corrections.
- 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
Correctionsโ
2026-08-27 (#1462) โ this ADR under-reported itself by twelve boxes.
ADR-019 is Accepted and every migration box was unchecked, while the migration had in fact
completed. Verified individually before ticking, not assumed from the status:
| box | evidence |
|---|---|
| Vitest installed | devDependencies.vitest present |
vitest.config.ts | file exists |
package.json scripts | test is vitest run |
| proof-of-concept, tool, utility, integration, performance tests | all present under tests/ |
| Jest dependencies removed | no jest / ts-jest / @types/jest in package.json |
| CI workflows updated | no jest in .github/workflows/*.yml |
| Jest configuration removed | no jest.config.* |
| migration scripts archived | scripts/migrate-to-vitest.sh removed in this change โ 73 lines, referenced nowhere, retirement.py returned RETIREMENT_REVIEW; git preserves it |
Zero files anywhere in src/ or tests/ still import from Jest.
The one box left unchecked, and whyโ
"Migrate failing test files first (4 files, ~59 tests)." This is not verifiable as written and is not this ADR's to close.
The framework migration is complete, but 70 tests are skipped across 14 files. #1477
measured 13 of them sharing one root cause: ESM mocking of ResearchOrchestrator, which is
also the ADR-018a violation in #1461. So the remaining work is not "finish migrating to
Vitest" โ it is "make ESM mocking work in this codebase", which is #751's scope, gated on
#1461.
Ticking it would claim a completion that 70 skipped tests contradict. Leaving it unchecked with the reason stated is the honest record; #751 and #1461 carry the work.
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