Jest → Vitest
Philosophy shift
Vitest reuses Vite’s transform pipeline. No separate Babel config, no separate module resolver — it shares the same config as your app. Tests run faster because the same bundler graph is reused.
Rule: If your project already uses Vite, Vitest is a drop-in replacement. If it doesn’t, weigh the cost of adding Vite before migrating.
Setup
Remove Jest:
npm uninstall jest babel-jest @types/jest ts-jest jest-environment-jsdom
Install Vitest:
npm install --save-dev vitest @vitest/coverage-v8
For DOM tests, also install:
npm install --save-dev @testing-library/jest-dom jsdom
Configure
vite.config.ts (or vitest.config.ts):
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/setupTests.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
},
},
});
Add to tsconfig.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Update package.json scripts:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
Core transformations
Imports
// Jest (no import needed with globals)
describe('...', () => { ... });
// Vitest — explicit import (recommended even with globals: true)
import { describe, it, expect, vi } from 'vitest';
Mocks
// Jest
jest.fn()
jest.spyOn(obj, 'method')
jest.mock('../module')
jest.clearAllMocks()
// Vitest
vi.fn()
vi.spyOn(obj, 'method')
vi.mock('../module')
vi.clearAllMocks()
Timers
// Jest
jest.useFakeTimers();
jest.runAllTimers();
jest.useRealTimers();
// Vitest
vi.useFakeTimers();
vi.runAllTimers();
vi.useRealTimers();
Module resets
// Jest
jest.resetModules();
jest.isolateModules(() => { ... });
// Vitest
vi.resetModules();
// isolateModules has no direct equivalent — use dynamic imports instead
Environment variables
// Jest
process.env.API_URL = 'http://test';
// Vitest — same syntax works, but prefer importMeta.env for Vite projects
import.meta.env.VITE_API_URL
When NOT to migrate
- Project doesn’t use Vite (still on CRA or custom Webpack) — Vitest’s main value comes from sharing the Vite pipeline.
- Heavy reliance on Jest-only APIs (
jest.requireActualinterplay, custom transformers) with no clean Vitest equivalent. - Snapshots require Jest-specific serializers not supported by Vitest.
Pitfalls
__mocks__directories work, but auto-mocking behavior differs slightly. Test withvi.mock()explicitly.jest.config.jsis replaced byvitest.config.ts— don’t maintain both.moduleNameMapper→resolve.aliasinvite.config.ts.testPathPatternCLI flag →vitest --reporter=verbose <pattern>.globals: truerequires tsconfig types — add"vitest/globals"to avoid TS errors ondescribe,it, etc.jest.setTimeout→vi.setConfig({ testTimeout: N })or per-test{ timeout: N }option.
Validation checklist
- All
jest.*calls replaced withvi.* - Explicit imports from
vitestin every test file (don’t rely solely on globals) -
jest.config.*deleted;vitest.config.tsdefines test setup -
moduleNameMappermigrated toresolve.aliasin vite config - All tests pass under
vitest run - CI scripts call
vitest(nojestbinary references remain)
Codemod references
- jest-to-vitest codemod — covers most mechanical replacements (
jest.*→vi.*). - Vitest migration guide lists what is NOT auto-convertible.
AI Prompt
You are migrating a test file from Jest to Vitest.
Rules:
1. Replace all `jest.*` calls with `vi.*` equivalents (jest.fn → vi.fn, jest.mock → vi.mock, etc.).
2. Add explicit imports: `import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'`.
3. Do not change test logic, assertions, or test descriptions.
4. Replace `jest.setTimeout(n)` with a `{ timeout: n }` option on the test/describe block.
5. Do not add or remove test cases.
Migrate the following file: