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
| Package | Weekly Downloads | Trend |
|---|---|---|
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.
Vitest: The Recommended Default
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.