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/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:

  1. Change imports (@jest/globals → vitest, 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​