Skip to main content

Bun Test vs Vitest vs Jest: JavaScript Test Runner Benchmarks 2026

·PkgPulse Team

TL;DR

Vitest is the 2026 default for JavaScript/TypeScript testing — Jest-compatible API, fast (esbuild-based), tight Vite integration, and native TypeScript. Bun test is 3-10x faster than Vitest but has lower compatibility with Jest ecosystem (missing some Jest APIs). Jest is legacy but still dominant at 20M+ downloads/week due to inertia and extensive plugin ecosystem. For new projects: Vitest. For Bun projects: Bun test.

Key Takeaways

  • Vitest: 4M downloads/week, Jest-compatible API, native TypeScript, Vite integration
  • Bun test: Built-in to Bun, 3-10x faster than Vitest, growing Jest compatibility
  • Jest: 20M downloads/week, massive ecosystem, slower (Node.js + Babel/SWC transform)
  • Speed: Bun test > Vitest > Jest (10-20x Jest slowdown on large codebases)
  • Migration: Vitest → Jest: mostly compatible; Bun test → Jest: some gaps
  • Coverage: All three support V8/Istanbul coverage

Downloads

PackageWeekly DownloadsTrend
jest~20M→ Slight decline
vitest~4M↑ Fast growing
bun (includes test)~1.5M↑ Fast growing

Speed Benchmarks

Project: 200 test files, 1500 test cases

Jest (SWC transform):
  Full run:    45s
  Watch mode:  8s per change

Vitest (Vite/esbuild):
  Full run:    12s  (3.7x faster than Jest)
  Watch mode:  1.5s (5x faster than Jest)

Bun test:
  Full run:    4s   (11x faster than Jest)
  Watch mode:  0.5s (16x faster than Jest)

Note: Gains are proportional to project size.
Small projects (< 50 tests): difference is marginal.

npm install -D vitest @vitest/coverage-v8
# For React:
npm install -D @testing-library/react @testing-library/user-event jsdom
// vitest.config.ts:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,         // describe, it, expect without import
    environment: 'jsdom',  // For DOM/React tests
    setupFiles: ['./src/test/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov', 'html'],
      thresholds: { global: { lines: 80 } },
    },
    // Concurrent test execution:
    pool: 'forks',         // Isolate test files
    poolOptions: {
      forks: { singleFork: false },
    },
  },
});
// src/test/setup.ts:
import '@testing-library/jest-dom';  // Adds toBeInTheDocument etc.
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';

afterEach(() => cleanup());
// user.test.ts — Vitest test:
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserProfile } from './UserProfile';

describe('UserProfile', () => {
  const mockUser = { id: '1', name: 'Alice', email: 'alice@example.com' };
  
  it('displays user information', () => {
    render(<UserProfile user={mockUser} />);
    
    expect(screen.getByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('alice@example.com')).toBeInTheDocument();
  });
  
  it('calls onUpdate when editing', async () => {
    const onUpdate = vi.fn();
    render(<UserProfile user={mockUser} onUpdate={onUpdate} />);
    
    fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
    fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'Bob' } });
    fireEvent.click(screen.getByRole('button', { name: 'Save' }));
    
    expect(onUpdate).toHaveBeenCalledWith({ ...mockUser, name: 'Bob' });
  });
});

Vitest vs Jest API Compatibility

// Vitest has full Jest API compatibility:
// ✅ describe, it, test, expect
// ✅ beforeAll, afterAll, beforeEach, afterEach
// ✅ vi.fn() = jest.fn()
// ✅ vi.mock() = jest.mock()
// ✅ vi.spyOn() = jest.spyOn()
// ✅ vi.useFakeTimers() = jest.useFakeTimers()
// ✅ expect.objectContaining, toMatchSnapshot, etc.

// Migration from Jest:
// 1. Replace jest.fn() → vi.fn() (or use globals)
// 2. Replace jest.mock() → vi.mock()
// 3. Remove babel-jest transform
// 4. Config migration (vitest.config.ts instead of jest.config.js)

Bun Test: Maximum Speed

// Bun uses built-in test runner — same file, no import needed:
import { describe, it, expect, mock, spyOn } from 'bun:test';

describe('UserProfile', () => {
  it('renders user name', () => {
    // DOM testing with Bun requires happy-dom:
    document.body.innerHTML = `<div id="user">Alice</div>`;
    expect(document.getElementById('user')?.textContent).toBe('Alice');
  });
  
  it('mocks a function', () => {
    const greet = mock((name: string) => `Hello, ${name}!`);
    
    expect(greet('Alice')).toBe('Hello, Alice!');
    expect(greet).toHaveBeenCalledTimes(1);
  });
  
  it('works with async', async () => {
    const fetchUser = async (id: string) => ({ id, name: 'Alice' });
    const user = await fetchUser('1');
    expect(user.name).toBe('Alice');
  });
});
# Bun test commands:
bun test                          # Run all tests
bun test --watch                  # Watch mode
bun test --coverage               # Coverage report
bun test src/components           # Run specific directory
bun test --only                   # Run only tests marked with test.only()
bun test --bail                   # Stop after first failure

Bun Test Limitations

Bun test gaps vs Vitest/Jest (2026):

  ❌ No jsdom environment (use happy-dom)
  ❌ Some Jest API differences (minor)
  ❌ No inline snapshots (snapshots work, inline doesn't)
  ❌ Limited @testing-library/react support
  ❌ No @vitest/coverage-v8 equivalent (has built-in)
  ⚠️  Some TypeScript decorators may behave differently

Use Bun test for:
  ✅ Pure TypeScript/JavaScript logic tests
  ✅ API/server route tests  
  ✅ Node.js utility library tests
  ✅ Integration tests without DOM

Use Vitest for:
  ✅ React component tests
  ✅ Browser environment simulation
  ✅ Projects using testing-library

Jest: Legacy but Still Dominant

npm install -D jest @jest/globals ts-jest
# Or with SWC (faster Jest):
npm install -D jest @swc/core @swc/jest
// jest.config.js with SWC (fastest Jest):
module.exports = {
  transform: {
    '^.+\\.(t|j)sx?$': ['@swc/jest'],
  },
  testEnvironment: 'jsdom',
  setupFilesAfterFramework: ['@testing-library/jest-dom'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss)$': 'identity-obj-proxy',
  },
};
When to keep Jest:
  → Existing large codebase with complex Jest config
  → Relying on Jest-specific plugins
  → Team expertise in Jest, migration cost not worth it
  → CI setup built around Jest

When to migrate Jest → Vitest:
  → CI times are too slow
  → New features need ESM native support
  → Starting a new project
  → Vite is already your build tool

Decision Guide

Use Vitest if:
  → New project (not Bun runtime)
  → React/Vue/Svelte components to test
  → Using Vite as build tool
  → Need Jest compatibility
  → Best balance of speed + ecosystem

Use Bun test if:
  → Already using Bun as runtime
  → Testing pure TypeScript logic (no DOM)
  → Maximum speed required
  → Server routes and API tests

Keep Jest if:
  → Large existing Jest codebase
  → Heavy reliance on Jest plugins
  → Not ready to migrate
  → Need maximum plugin compatibility

Compare Vitest, Jest, and Bun download trends on PkgPulse.

Comments

Stay Updated

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