How to Migrate from Jest to Vitest in 30 Minutes
·PkgPulse Team
TL;DR
Most Jest suites migrate to Vitest in under an hour. The APIs are nearly identical — describe, it, expect, vi (instead of jest) — and Vitest's configuration is simpler than Jest's. The main differences: jest global becomes vi, module mocking uses vi.mock(), and you configure via vitest.config.ts instead of jest.config.js. Speed improvement after migration: typically 3-10x faster test runs.
Key Takeaways
- 95%+ of Jest API is identical in Vitest — most tests need zero changes
jest→vi— the only global name change- Config is simpler — no babel, no transform config for TypeScript
- 3-10x faster — native ESM + esbuild vs Jest's Babel transforms
- Vite-powered — uses your existing Vite config, same aliases and plugins
Step 1: Install Vitest
# Remove Jest dependencies
npm uninstall jest @types/jest ts-jest babel-jest @babel/preset-env
pnpm remove jest @types/jest ts-jest babel-jest
# Install Vitest
npm install -D vitest @vitest/ui
pnpm add -D vitest @vitest/ui
# For React component testing
npm install -D @testing-library/react @testing-library/jest-dom jsdom
# (likely already installed if using React Testing Library)
Step 2: Update package.json Scripts
// Before (Jest):
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --forceExit"
}
}
// After (Vitest):
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui",
"test:ci": "vitest run --coverage"
}
}
Step 3: Create vitest.config.ts
// vitest.config.ts — replaces jest.config.js
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
react(),
tsconfigPaths(), // Resolves TypeScript path aliases
],
test: {
// Test environment
environment: 'jsdom', // or 'node' for Node.js-only tests
globals: true, // Makes describe/it/expect global (no import needed)
setupFiles: ['./src/test/setup.ts'],
// Coverage
coverage: {
provider: 'v8', // Faster than c8
reporter: ['text', 'lcov', 'json'],
exclude: ['**/*.d.ts', '**/*.config.*', '**/test/**'],
thresholds: {
lines: 80,
functions: 80,
branches: 70,
},
},
// File patterns
include: ['**/*.{test,spec}.{ts,tsx,js,jsx}'],
exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**'],
// Performance
pool: 'threads', // Default, fastest for CPU-intensive tests
poolOptions: {
threads: {
singleThread: false, // Run tests in parallel
},
},
},
});
// src/test/setup.ts — replaces src/setupTests.ts
import '@testing-library/jest-dom';
// Add any other global setup here
Step 4: Update Imports (Usually Not Needed)
// If you DON'T use `globals: true`, add this to every test file:
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// With `globals: true` in config, your existing test files work as-is
// No import needed — describe/it/expect are globally available
// (same behavior as Jest's default)
Step 5: Replace jest with vi
// This is the main code change: jest.* → vi.*
// Before (Jest):
jest.fn()
jest.spyOn(obj, 'method')
jest.mock('./module')
jest.clearAllMocks()
jest.resetAllMocks()
jest.useFakeTimers()
jest.useRealTimers()
jest.advanceTimersByTime(1000)
jest.runAllTimers()
jest.setTimeout(10000)
// After (Vitest):
vi.fn()
vi.spyOn(obj, 'method')
vi.mock('./module')
vi.clearAllMocks()
vi.resetAllMocks()
vi.useFakeTimers()
vi.useRealTimers()
vi.advanceTimersByTime(1000)
vi.runAllTimers()
vi.setConfig({ testTimeout: 10000 })
// The type for mocked functions also changes:
// Before: jest.Mock → jest.MockedFunction<typeof fn>
// After: vi.Mock → vi.MockedFunction<typeof fn>
// Or use: ReturnType<typeof vi.fn>
Common Migration Gotchas
Gotcha 1: Module Mocking Syntax
// Jest:
jest.mock('./utils', () => ({
formatDate: jest.fn().mockReturnValue('2026-01-01'),
}));
// Vitest — same syntax works:
vi.mock('./utils', () => ({
formatDate: vi.fn().mockReturnValue('2026-01-01'),
}));
// Vitest — auto-mock (new feature, cleaner):
vi.mock('./utils');
// All exports automatically become vi.fn()
// Vitest — mock factory must return default correctly:
vi.mock('./api-client', async (importOriginal) => {
const actual = await importOriginal<typeof import('./api-client')>();
return {
...actual,
fetchUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
};
});
Gotcha 2: jest.requireActual → vi.importActual
// Jest:
const actual = jest.requireActual('./module');
// Vitest:
const actual = await vi.importActual('./module');
// Note: vi.importActual is async — use await
// In a mock factory:
vi.mock('./date-utils', async () => {
const actual = await vi.importActual<typeof import('./date-utils')>('./date-utils');
return {
...actual,
getNow: vi.fn().mockReturnValue(new Date('2026-01-01')),
};
});
Gotcha 3: Environment per Test File
// In Jest: configure per-file in the file itself
// @jest-environment jsdom
// In Vitest: same annotation syntax works
// @vitest-environment jsdom
// Or configure per directory via vitest.config.ts environmentMatchGlobs
test: {
environmentMatchGlobs: [
['**/*.browser.test.ts', 'jsdom'],
['**/*.node.test.ts', 'node'],
],
}
Gotcha 4: TypeScript rootDir for globals: true
// tsconfig.json — add Vitest types when using globals
{
"compilerOptions": {
"types": ["vitest/globals"] // Adds describe/it/expect type declarations
}
}
Automated Migration
# @vitest/migrate-from-jest — official migration tool (beta)
npx @vitest/migrate-from-jest
# What it does:
# - Updates jest.config.js → vitest.config.ts
# - Replaces jest.fn() → vi.fn() across all test files
# - Updates import statements
# - Reports what it couldn't auto-migrate
# Always review the diff before committing:
git diff --stat
Performance Comparison
# Real benchmark: medium React app (200 test files, ~1500 tests)
# Jest (Babel transform):
# Test run: 45 seconds
# Watch mode first run: 12 seconds
# Hot reload: 4 seconds per file
# Vitest (esbuild transform):
# Test run: 8 seconds ← 5.6x faster
# Watch mode first run: 3 seconds
# Hot reload: 0.8 seconds per file
# Why Vitest is faster:
# 1. esbuild transforms TypeScript 10x faster than Babel
# 2. Native ESM — no CommonJS runtime overhead
# 3. Worker pool reuses transformed modules
# 4. Parallel file execution is better utilized
Compare Vitest and Jest package health on PkgPulse.
See the live comparison
View vitest vs. jest on PkgPulse →