Skip to main content

The Consolidation of JavaScript Testing: How Vitest Won

·PkgPulse Team

TL;DR

Vitest won new project adoption. Jest wins by legacy volume. In 2026, ~70% of new JavaScript projects choosing a test runner pick Vitest. Jest still has ~18M weekly downloads vs Vitest's ~8M, but the gap is closing fast. The story: Jest was built for CommonJS + Babel in 2014. Vitest was built for ESM + Vite in 2021. When the ecosystem moved to ESM, Vite, and TypeScript-first development, Vitest was already there. Jest scrambled to add ESM support, but Vitest's native integration was simply better.

Key Takeaways

  • Vitest: ~8M weekly downloads — growing ~150% YoY; dominant for new projects
  • Jest: ~18M weekly downloads — still dominant by volume; legacy codebases
  • Vitest new project share — ~70% of new projects choosing a test runner in 2026
  • Key differentiator — Vitest shares your Vite config; Jest requires separate Babel/transform config
  • API compatibility — Vitest's API is Jest-compatible; migrations take 1-2 hours

Why Vitest Won

1. Native ESM and TypeScript

// Jest with TypeScript — requires transform setup
// jest.config.js
module.exports = {
  preset: 'ts-jest',
  // OR:
  transform: {
    '^.+\\.tsx?$': ['babel-jest', { presets: ['@babel/preset-typescript'] }],
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',  // Path aliases need manual config
  },
};
// package.json deps: jest, @types/jest, ts-jest or babel-jest, @babel/preset-typescript
// Total: 5+ packages for TypeScript support

// Vitest with TypeScript — just works
// vitest.config.ts (or vite.config.ts)
import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: {
    // That's it. TypeScript works natively.
  },
});
// package.json deps: vitest
// Total: 1 package

2. Shared Vite Config (Zero Duplication)

// Vitest reuses your Vite config — no duplication
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  resolve: {
    alias: { '@': '/src' },
  },
  // test section only for Vitest — everything else is shared
  test: {
    environment: 'happy-dom',
    globals: true,
  },
});

// Jest equivalent requires duplicating ALL of this:
// - Transform config (must match Vite's plugin list)
// - Path aliases (moduleNameMapper)
// - Mock handling (moduleFileExtensions)
// Every Vite plugin needs a Jest equivalent

3. Speed

# Cold start comparison (medium project, 200 test files)
Jest (ts-jest):      ~6,200ms
Jest (babel-jest):   ~4,800ms
Vitest:              ~520ms   (10x faster cold start)

# Watch mode (single file change):
Jest:    ~800ms  (re-processes affected files)
Vitest:  ~80ms   (Vite's incremental HMR approach)

# The developer loop impact:
# Save → test feedback in 80ms (Vitest) vs 800ms (Jest)
# That's the difference between "instant feedback" and "wait to see result"

4. In-Source Testing

// Vitest's unique feature: tests alongside source code
// src/lib/health-score.ts

export function calculateHealthScore(pkg: Package): number {
  // ... implementation
}

// In-source tests (only run during `vitest`, not in production build)
if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;

  it('returns 100 for perfect package', () => {
    expect(calculateHealthScore({ weeksSinceLastRelease: 1 })).toBe(100);
  });
}

The Migration Path

From Jest to Vitest (1-2 hours)

# Step 1: Install Vitest
npm uninstall jest ts-jest babel-jest @types/jest
npm install -D vitest @vitest/coverage-v8 happy-dom

# Step 2: Create vitest.config.ts (or update vite.config.ts)
# test: { globals: true, environment: 'happy-dom' }

# Step 3: Update package.json scripts
# "test": "vitest" (was: "jest")
# "test:coverage": "vitest run --coverage"

# Step 4: Run vitest — 90% of Jest tests work unchanged
npx vitest run

# Step 5: Fix differences (usually minimal)
// Common differences to fix:

// 1. jest.mock → vi.mock
// Before: jest.mock('./api');
// After:  vi.mock('./api');

// 2. jest.fn() → vi.fn()
// Before: const mock = jest.fn();
// After:  const mock = vi.fn();

// 3. jest.useFakeTimers → vi.useFakeTimers
// API is identical, just rename jest → vi

// 4. Import vi (if not using globals)
import { vi, describe, it, expect } from 'vitest';

// 5. jest.spyOn → vi.spyOn (identical API)
const spy = vi.spyOn(console, 'log');

Download Trend Analysis

Weekly downloads (actual data):

2022 Q1:
  Jest:    15M
  Vitest:  300K  (launched Feb 2021)
  Ratio:   50:1

2023 Q1:
  Jest:    17M
  Vitest:  2M
  Ratio:   8.5:1

2024 Q1:
  Jest:    18M
  Vitest:  5M
  Ratio:   3.6:1

2026 Q1:
  Jest:    18M  (plateaued)
  Vitest:  8M   (still growing)
  Ratio:   2.25:1

Projection: Vitest surpasses Jest downloads by 2027-2028

What's Still Better in Jest

Vitest didn't win everything:

// Jest advantages in 2026:

// 1. Mature ecosystem — more third-party matchers
import { toHaveNoViolations } from 'jest-axe';   // Accessibility testing
import { toMatchInlineSnapshot } from 'jest-snapshot'; // Better snapshots
// Vitest supports most, but jest-specific matchers need @testing-library/jest-dom

// 2. Snapshot testing — Jest's .toMatchSnapshot() is the gold standard
// Vitest supports it but the UX is slightly different for inline snapshots

// 3. React Native testing — Jest is still the standard
// @testing-library/react-native, jest-react-native — all Jest-first
// Vitest for React Native: possible but less tooling

// 4. Some CI environments — Jest's error output is more familiar to teams
// Minor, but real: teams may need to update CI scripts

The E2E Side: Playwright Won Faster

While Vitest vs Jest played out slowly, the E2E battle was decisive:

E2E testing download trends (2026):
Playwright: ~5M weekly downloads (+300% since 2022)
Cypress:    ~5M weekly downloads (flat since 2023)

New project selection:
Playwright:  ~65% of new projects choosing E2E testing
Cypress:     ~30%
WebdriverIO: ~5%

Playwright's key advantages:
✅ Cross-browser (Chromium, Firefox, WebKit)
✅ Multi-tab, multi-window, mobile viewport
✅ Auto-waiting (no flaky timeouts)
✅ Built-in codegen (record tests by clicking)
✅ Trace viewer (visual replay of failures)
✅ Free (Cypress component testing is paywalled)

Testing Stack Recommendations for 2026

Project TypeUnit TestsComponent TestsE2E
New React/ViteVitestVitest + Testing LibraryPlaywright
New Next.jsVitestVitest + Testing LibraryPlaywright
Existing Jest codebaseMigrate to Vitest OR stay JestSameMigrate to Playwright
React NativeJestJest + Testing LibraryDetox
Node.js APIVitestN/APlaywright or Supertest
LibraryVitestVitestN/A (consumers test)

Compare testing library package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.