Skip to main content

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:

  1. Ensure your project uses Vite. Vitest is built for Vite. If you're still on Webpack, consider upgrading Vite first.
  2. Verify Jest version compatibility. If you're on Jest 27+, the migration is easier (newer API).
  3. List all Jest dependencies you've installed explicitly (not auto-included).
  4. Review custom Jest config in jest.config.js or package.json.

Typical Jest setup:

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:

JestVitestFix
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.ts and copy aliases/resolvers.
  • Change imports: @jest/globalsvitest, 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:

  1. Change imports (@jest/globalsvitest, jest.vi.).
  2. Update config files.
  3. 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.

Further Reading