Migrate From Jest to Vitest: Step-by-Step
Migrating from Jest to Vitest is straightforward because Vitest's API is nearly identical to Jest's. You can often reuse 90% of your tests without changes. The main benefits: Vitest runs 3–5x faster (no CommonJS transpilation overhead), integrates seamlessly with Vite, and provides a better DX (watch mode, on-demand test runs). This article walks through a real migration, step-by-step (Vitest documentation, 2026).
Pre-Migration Checklist
Before starting:
- Ensure your project uses Vite. Vitest is built for Vite. If you're still on Webpack, consider upgrading Vite first.
- Verify Jest version compatibility. If you're on Jest 27+, the migration is easier (newer API).
- List all Jest dependencies you've installed explicitly (not auto-included).
- Review custom Jest config in
jest.config.jsorpackage.json.
Typical Jest setup:
npm ls jest
# [email protected]
# @types/[email protected]
# [email protected]
# [email protected]
Step 1: Install Vitest and Remove Jest
Uninstall Jest dependencies:
npm uninstall jest @types/jest jest-environment-jsdom babel-jest jest-mock-extended
Install Vitest (the example uses React):
npm install -D vitest jsdom @vitest/ui @testing-library/react @testing-library/user-event
Step 2: Create vitest.config.ts
Create vitest.config.ts (or add to existing vite.config.ts):
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'dist/', '**/*.test.ts'],
},
},
});
Copy any aliases or resolver config from your jest.config.js into vitest.config.ts.
Step 3: Create vitest.setup.ts
Create a setup file (equivalent to Jest's setupFilesAfterEnv):
import { afterEach, expect } from 'vitest';
import { cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
// Cleanup after each test (required for React Testing Library)
afterEach(() => {
cleanup();
});
// Optional: add any other global setup here
If your Jest setupFilesAfterEnv had multiple files, combine them into one or reference them:
// Old Jest setup:
// setupFilesAfterEnv: ['./setup/setup.js', './setup/mocks.js']
// New Vitest setup in vitest.setup.ts:
import './setup/setup.js';
import './setup/mocks.js';
Step 4: Update package.json Scripts
Replace Jest scripts with Vitest:
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:ci": "vitest --run --reporter=verbose"
}
}
Step 5: Port Test Files (Usually Zero Changes)
Most test files work unchanged. The main API is identical:
// ✓ This Jest test works in Vitest unchanged
import { describe, it, expect, vi, beforeEach, afterEach } from 'jest'; // ← Only import change
describe('Button', () => {
it('calls onClick when clicked', () => {
// Test code unchanged
});
});
Change only the import statement:
// Jest:
import { describe, it, expect, vi } from '@jest/globals';
// Vitest:
import { describe, it, expect, vi } from 'vitest';
If you're using globals: true (recommended), you don't even need imports:
describe('Button', () => {
it('calls onClick when clicked', () => {
// No imports needed; describe, it, expect are global
});
});
Step 6: Handle API Differences (Usually None)
Vitest is API-compatible with Jest. The rare differences:
| Jest | Vitest | Fix |
|---|---|---|
jest.mock() | vi.mock() | Rename in mocks |
jest.fn() | vi.fn() | Rename in tests |
jest.useFakeTimers() | vi.useFakeTimers() | Rename |
jest.spyOn() | vi.spyOn() | Rename |
jest.requireActual() | vi.importActual() | Rename |
Find-and-replace across your test files:
# Replace jest. with vi.
sed -i 's/jest\./vi./g' **/*.test.ts
# Replace jest imports with vitest
sed -i "s/@jest\/globals/vitest/g" **/*.test.ts
For macOS (different sed syntax):
sed -i '' 's/jest\./vi./g' **/*.test.ts
sed -i '' 's/@jest\/globals/vitest/g' **/*.test.ts
Step 7: Migrate Custom Jest Config
If you have custom Jest config, port it to Vitest:
Jest moduleNameMapper → Vitest resolve.alias
Jest:
// jest.config.js
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
},
};
Vitest:
// vitest.config.ts
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
test: {
// CSS imports are auto-mocked in jsdom
},
});
Jest testEnvironmentOptions → Vitest test.environment config
Jest:
module.exports = {
testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost',
},
};
Vitest:
test: {
environment: 'jsdom',
// jsdom is pre-configured with safe defaults; custom options not needed usually
}
Jest transformIgnorePatterns → Vitest test.include/exclude
Jest:
module.exports = {
transformIgnorePatterns: ['node_modules/(?!(some-esm-module)/)'],
};
Vitest:
test: {
deps: {
inline: ['some-esm-module'], // Pre-bundle this ES module
},
}
Step 8: Run Tests and Fix Failures
Run the test suite:
npm test -- --run
Vitest will report any failures. Common issues:
Issue: "Cannot find module" error
Cause: Module alias not configured.
Fix: Ensure resolve.alias in vitest.config.ts matches your code:
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
},
}
Issue: "globals not defined" error
Cause: globals: true not set.
Fix: Add to vitest.config.ts:
test: {
globals: true,
}
Issue: Snapshot mismatches
Cause: Vitest formats snapshots slightly differently than Jest.
Fix: Update all snapshots:
npm test -- --update
Review the diffs to ensure they're cosmetic (whitespace, formatting).
Issue: "XMLHttpRequest is not defined" error
Cause: Some old tests use XMLHttpRequest directly (deprecated).
Fix: Use fetch instead, or mock it:
vi.stubGlobal('fetch', vi.fn());
Step 9: Verify Coverage Migration
Update coverage reporter:
Jest:
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
Vitest:
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'dist/', '**/*.test.ts'],
}
Run coverage:
npm run test:coverage
Compare results to previous Jest coverage. They may differ slightly (Vitest uses V8, Jest uses Istanbul), but should be within 1–2%.
Step 10: Update CI/CD Pipeline
If you have a GitHub Actions or other CI pipeline, update the test command:
# GitHub Actions
- name: Run tests
run: npm test -- --run --reporter=verbose
Vitest is faster, so CI should complete quicker. Monitor the first few runs to confirm.
Full Migration Example
Here's a before/after for a real project:
Before (Jest):
jest.config.js
package.json (with Jest scripts + jest dependency)
src/Button.test.tsx (imports from '@jest/globals')
src/__mocks__/api.ts
setupTests.ts
After (Vitest):
vitest.config.ts (replaces jest.config.js)
vitest.setup.ts (replaces setupTests.ts)
package.json (vitest scripts, vitest dependency)
src/Button.test.tsx (imports from 'vitest')
src/__mocks__/api.ts (unchanged)
Commands:
# Remove Jest
npm uninstall jest @types/jest jest-environment-jsdom
# Install Vitest
npm install -D vitest jsdom @vitest/ui
# Update test scripts in package.json
# Create vitest.config.ts
# Create vitest.setup.ts
# Update imports in test files (jest. → vi., @jest/globals → vitest)
# Run tests
npm test -- --run
Key Takeaways
- Vitest is API-compatible with Jest; most tests work unchanged.
- Replace Jest config with
vitest.config.tsand copy aliases/resolvers. - Change imports:
@jest/globals→vitest,jest.→vi.. - Run tests and fix module resolution / alias errors.
- Update snapshots if formatting differs (usually cosmetic).
- Verify coverage matches previous Jest reports.
- Update CI/CD to use
npm test -- --run.
Frequently Asked Questions
How long does a typical migration take?
For a small project (< 50 tests): 30 minutes to 1 hour. For a medium project (50–500 tests): 2–3 hours (mostly find-and-replace). For a large project (> 500 tests): 4–6 hours (plus debugging edge cases).
Do I need to rewrite all my tests?
No. Vitest's API is nearly identical to Jest. You only need to:
- Change imports (
@jest/globals→vitest,jest.→vi.). - Update config files.
- Fix module aliases.
The actual test code stays the same.
Will tests run faster after migration?
Yes, 3–5x faster on typical projects. The improvement comes from Vitest's native ES module pipeline (no CommonJS transpilation overhead) and better file watching.
Can I keep using Jest alongside Vitest?
Not recommended. It's confusing to have two test runners. Migrate completely, then delete Jest config.
What if my project doesn't use Vite?
Vitest works with non-Vite projects, but the benefits diminish. If you're on Webpack, consider upgrading to Vite first, then migrating to Vitest. If staying on Webpack, Jest is a safer choice.
How do I handle external packages that Jest mocked differently?
Vitest's vi.mock() is drop-in compatible with jest.mock(). If you have custom Jest mocks in __mocks__/, they work unchanged with Vitest.