Overview

Use Vitest for new projects. It shares the Vite config, natively supports ESM and TypeScript without transforms, and runs tests in parallel with HMR-speed feedback. Use Jest when the project predates Vite, runs on CommonJS, or depends on Jest-specific plugins such as jest-circus, jest-environment-jsdom with legacy setup, or community plugins that lack Vitest ports. See vite-vs-webpack for the upstream bundler decision.

When Vitest wins

Vitest is the right test runner for projects that already use Vite or are being built from scratch.

  • Zero config with Vite: Vitest reads vite.config.ts directly. Aliases, module resolution, and plugins defined there apply to tests without duplication.
  • Native ESM: imports and dynamic import() work without Babel transforms or Jest’s experimental --experimental-vm-modules flag.
  • TypeScript out of the box: no ts-jest, no babel-jest. Vitest strips types natively using esbuild.
  • Speed: Vitest runs in Vite’s worker pool. A cold run on a 500-test suite is typically 2 to 4 times faster than Jest on the same hardware, and re-runs with --watch are near-instant.
  • Compatible API: describe, it, expect, vi.mock, vi.spyOn map directly to Jest equivalents. Migration of test bodies is mostly a find-and-replace of jest.* to vi.*.
  • UI mode: vitest --ui opens a browser panel with test results, coverage, and logs. Useful when debugging flaky tests.

When Jest wins

Jest remains the correct choice in three situations.

  • Legacy CommonJS codebase not using Vite: Jest’s transform pipeline handles CommonJS naturally. Migrating to Vitest also means migrating the module system, which is a separate risk.
  • Snapshot testing with custom serializers: Jest’s snapshot ecosystem and jest-snapshot tooling are mature. Vitest snapshots work but some third-party serializers have no Vitest port.
  • Specific plugin dependencies: jest-axe for accessibility assertions, jest-fetch-mock, or enterprise CI plugins that hook into Jest’s reporter API. Check Vitest compatibility before committing.
  • React Native: Jest is the standard test runner for React Native. Vitest does not support React Native’s Metro bundler environment.

Trade-offs at a glance

DimensionVitestJest
ConfigShares vite.config.tsSeparate jest.config.*
ESM supportNativeExperimental flag or Babel transform
TypeScriptNative (esbuild strip)ts-jest or babel-jest
Cold run speedFast (esbuild, parallel workers)Slower on large suites
Watch modeHMR-speed re-runsFile-watch re-runs; slower
Plugin ecosystemGrowing; most common cases coveredMature; deep third-party support
React NativeNot supportedStandard
API compatibilityJest-compatible; vi.* not jest.*Canonical
CoverageBuilt-in (v8 or istanbul)--coverage via istanbul
Community sizeGrowing rapidlyLarge; established
Snapshot testingSupported; fewer serializer optionsMature ecosystem

Migration cost

Jest to Vitest migration is typically low-effort for Vite projects.

  • Replace jest with vitest in package.json. Update jest.config.* to a test block in vite.config.ts.
  • Global jest.* calls become vi.*: jest.mock to vi.mock, jest.spyOn to vi.spyOn, jest.fn to vi.fn. A codemod or find-and-replace handles 90 percent of cases.
  • @testing-library packages work unchanged. jsdom is still the default environment for browser-like tests.
  • Watch for jest.useFakeTimers semantics; Vitest’s fake timer API is mostly compatible but has minor differences around setSystemTime.
  • Effort: one to three engineer-days for a 200-test suite with no exotic plugins.

Recommendation

  • New Vite project: Vitest. No exceptions for greenfield work.
  • New project without Vite: Vitest still works but requires configuring the environment separately. Evaluate whether adopting Vite as well is worth it; often it is.
  • Existing Jest project on CommonJS: stay on Jest. Migrate when the project migrates to ESM or Vite.
  • React Native: Jest.
  • Mixed monorepo: run Vitest in the web packages and Jest in the React Native package. They coexist.