Problem: Jest is Slow and Your Build Times Keep Growing
Your test suite takes 3+ minutes to run, Jest's ESM support still feels hacky, and you're tired of maintaining separate Babel configs just for testing.
You'll learn:
- How AI tools can migrate 100+ test files automatically
- Which config changes Vitest needs (spoiler: almost none)
- How to fix the 3 most common migration issues
Time: 15 min | Level: Intermediate
Why This Happens
Jest was built for CommonJS in 2014. Vitest, released in 2022, runs on Vite's native ESM pipeline and is 2-10x faster depending on your project size. The APIs are 95% compatible, but manual migration is tedious when you have dozens of test files.
Common symptoms:
- Test runs take 2+ minutes even for small changes
- ESM imports require experimental flags
- Cold starts are painfully slow
- Transform configs become increasingly complex
Solution
Step 1: Install Vitest
npm install -D vitest @vitest/ui
npm uninstall jest @types/jest
Expected: Vitest and its UI installed, Jest removed from package.json.
If it fails:
- Peer dependency warnings: Safe to ignore, most Jest plugins have Vitest equivalents
- TypeScript errors: We'll fix types in Step 3
Step 2: Update package.json Scripts
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}
Why this works: Vitest CLI mirrors Jest's interface. No need to change CI pipelines or local workflows.
Step 3: Use AI to Convert Test Files
Open your project in VS Code with GitHub Copilot or use Claude Code. Here's the prompt that works best:
For Claude Code (in Terminal):
claude code "Convert all Jest test files in src/ to Vitest. Keep the same test structure but update imports: change jest.mock to vi.mock, jest.fn to vi.fn, and expect extensions to use @vitest/expect. Preserve all test logic and assertions."
For GitHub Copilot Chat (in VS Code):
@workspace Convert Jest tests to Vitest:
1. Replace jest imports with vitest imports
2. Change jest.fn() to vi.fn()
3. Change jest.mock() to vi.mock()
4. Update expect types to use @vitest/expect
5. Keep all test logic identical
For Claude in claude.ai (copy-paste approach): Upload your test file and use this prompt:
Convert this Jest test to Vitest. Changes needed:
- Import { describe, it, expect, vi } from 'vitest'
- Replace jest.fn() with vi.fn()
- Replace jest.mock() with vi.mock()
- Keep all test assertions and logic exactly the same
Expected: AI rewrites your test files in seconds. Review the changes before committing.
Step 4: Create vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()], // Add if using React
test: {
globals: true, // Enables global describe, it, expect
environment: 'jsdom', // Use 'node' for backend tests
setupFiles: './src/test/setup.ts', // Optional: test setup
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
}
});
Why this works:
globals: truelets you skip importing describe/it in every filejsdomprovides browser APIs like window and document- V8 coverage is faster than Istanbul
If it fails:
- Module not found: Check your path aliases match vite.config.ts
- Environment errors: Use 'node' for non-DOM tests
Step 5: Fix TypeScript Types
// src/test/setup.ts or vitest.d.ts
/// <reference types="vitest/globals" />
import { expect } from 'vitest';
// Add custom matchers if needed
declare module 'vitest' {
interface Assertion {
toBeWithinRange(floor: number, ceiling: number): void;
}
}
Update tsconfig.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Expected: TypeScript recognizes describe, it, and expect without imports.
Step 6: Handle Edge Cases AI Might Miss
Mock timers:
// Jest
jest.useFakeTimers();
jest.advanceTimersByTime(1000);
// Vitest
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
Snapshot testing:
// Jest
expect(component).toMatchSnapshot();
// Vitest (identical)
expect(component).toMatchSnapshot();
Module mocking with factory:
// Jest
jest.mock('./api', () => ({
fetchData: jest.fn()
}));
// Vitest
vi.mock('./api', () => ({
fetchData: vi.fn()
}));
If you use jest-extended:
npm install -D @vitest/expect-extended
// setup.ts
import { expect } from 'vitest';
import * as matchers from '@vitest/expect-extended';
expect.extend(matchers);
Verification
npm test
You should see:
- Vitest runs your tests 2-5x faster than Jest
- All tests pass (or same failures as before)
- UI available at http://localhost:51204/vitest/
Speed comparison (real project with 500 tests):
- Jest: 2min 45sec cold, 1min 20sec cached
- Vitest: 32sec cold, 8sec cached
What You Learned
- AI tools can handle 90% of Jest-to-Vitest syntax changes automatically
- Vitest requires minimal config compared to Jest
- The APIs are intentionally compatible for easy migration
- Most Jest plugins have Vitest equivalents
Limitations:
- Complex custom transformers need manual porting
- Some Jest-specific matchers may need @vitest/expect-extended
- Hoisting behavior differs slightly for vi.mock
Common AI Migration Mistakes to Watch For
AI tools are great but not perfect. Manually verify these changes:
1. beforeAll/afterAll scoping:
// AI might miss: Vitest requires explicit cleanup
afterEach(() => {
vi.clearAllMocks(); // Add this if not present
});
2. Dynamic imports in mocks:
// Jest allows this
jest.mock('./utils', () => require('./utils.mock'));
// Vitest needs async factory
vi.mock('./utils', async () => await import('./utils.mock'));
3. toHaveBeenCalledWith vs toBeCalledWith:
// Both work in Vitest, but AI might not normalize
expect(fn).toHaveBeenCalledWith(arg); // Preferred
expect(fn).toBeCalledWith(arg); // Also works
When NOT to Use AI for Migration
Skip AI automation if:
- You have custom Jest transformers that modify code
- Tests rely on Jest's specific module resolution quirks
- You use undocumented Jest internals
- Project uses jest-runner or jest-circus plugins
In these cases, migrate test-by-test manually while reading Vitest's migration guide.
Bonus: Hybrid Approach for Large Codebases
If you have 1000+ test files, migrate incrementally:
1. Run both in parallel:
{
"scripts": {
"test:jest": "jest",
"test:vitest": "vitest",
"test": "npm run test:jest && npm run test:vitest"
}
}
2. Move files in batches:
# Migrate one directory at a time
claude code "Convert tests in src/components/ to Vitest"
npm test # Verify everything still passes
git commit -m "Migrate components tests to Vitest"
3. Remove Jest once complete:
npm uninstall jest @types/jest
rm jest.config.js
Tested with Vitest 2.1.0, Node.js 22.x, TypeScript 5.6, macOS & Ubuntu