Bun Test vs Vitest vs Jest 2026: Speed Compared
Bun's test runner completes in 0.08 seconds what takes Jest 1.2 seconds and Vitest 0.9 seconds. Vitest runs 10-20x faster than Jest in watch mode with HMR. Jest has 55M+ weekly downloads and the most mature ecosystem. The choice comes down to what you optimize: raw speed, framework integration, or ecosystem breadth.
TL;DR
Vitest for Vite-based projects and most new TypeScript applications — 10-20x faster than Jest, Jest-compatible API, excellent HMR in watch mode. Bun test when you're already using Bun as your runtime and want the absolute fastest test execution. Jest for existing projects with complex configuration, Jest-specific plugins (Enzyme, jest-axe, etc.), or non-Vite setups that don't need migration overhead. For new projects starting today, Vitest is the pragmatic default.
Key Takeaways
- Jest: 55M weekly downloads, mature ecosystem, 0.x startup overhead
- Vitest: 6M weekly downloads, Vite integration, HMR watch mode, 10-20x faster than Jest
- Bun test: Built-in (no install), 0.08s for simple suites, Jest-compatible API
- All three: describe/it/test, expect, mocking (vi.mock / jest.mock / mock()), snapshots
- Vitest: Native ESM support, native TypeScript (via Vite), browser mode (Playwright)
- Jest: jsdom by default, extensive plugin ecosystem, monorepo-tested
- Bun test: Built-in coverage, requires Bun runtime, snapshot testing
Speed Comparison
# Test suite: 50 tests across 10 files, TypeScript, mocking
# Cold run (no cache):
Jest: ~1.2s
Vitest: ~0.9s
Bun: ~0.08s
# Watch mode (single file change):
Jest: ~800ms (re-runs all)
Vitest: ~40ms (HMR — only re-runs changed)
Bun: ~50ms (watch mode)
Speed matters most in watch mode during development. Vitest's HMR re-runs only affected tests in ~40ms — near-instant feedback compared to Jest's 800ms.
Jest
Package: jest, @jest/core, ts-jest
Weekly downloads: 55M
GitHub stars: 44K
Creator: Facebook / Meta
Jest is the established standard: it ships with Create React App, NestJS, and most major frameworks by default. Its ecosystem is the largest of the three.
Installation
npm install -D jest ts-jest @types/jest
# Or with Babel for TypeScript:
npm install -D jest babel-jest @babel/preset-typescript @babel/core
Configuration
// jest.config.json
{
"preset": "ts-jest",
"testEnvironment": "node",
"roots": ["<rootDir>/src"],
"testMatch": ["**/*.spec.ts", "**/*.test.ts"],
"moduleNameMapper": {
"@/(.*)": "<rootDir>/src/$1"
},
"setupFilesAfterEach": ["<rootDir>/jest.setup.ts"],
"collectCoverageFrom": ["src/**/*.ts", "!src/**/*.d.ts"]
}
Basic Test
// user.service.test.ts
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
// Jest mock
jest.mock('./user.repository');
const MockedUserRepository = UserRepository as jest.MockedClass<typeof UserRepository>;
describe('UserService', () => {
let service: UserService;
let mockRepo: jest.Mocked<UserRepository>;
beforeEach(() => {
MockedUserRepository.mockClear();
mockRepo = new MockedUserRepository() as jest.Mocked<UserRepository>;
service = new UserService(mockRepo);
});
it('returns user by id', async () => {
mockRepo.findById.mockResolvedValue({
id: '123',
name: 'Alice',
email: 'alice@example.com',
});
const user = await service.getUser('123');
expect(user.name).toBe('Alice');
expect(mockRepo.findById).toHaveBeenCalledWith('123');
expect(mockRepo.findById).toHaveBeenCalledTimes(1);
});
it('throws when user not found', async () => {
mockRepo.findById.mockResolvedValue(null);
await expect(service.getUser('999')).rejects.toThrow('User 999 not found');
});
});
Jest for React (with Testing Library)
npm install -D @testing-library/react @testing-library/jest-dom jest-environment-jsdom
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders label', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});
it('calls onClick', () => {
const onClick = jest.fn();
render(<Button onClick={onClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
});
Jest Snapshot Testing
it('renders button correctly', () => {
const { asFragment } = render(<Button variant="primary">Submit</Button>);
expect(asFragment()).toMatchSnapshot();
});
Jest Strengths
- Largest ecosystem: jest-axe, jest-fetch-mock, jest-canvas-mock, Enzyme
- Snapshot testing with automatic diff display
- Most documentation and Stack Overflow answers
- Works with any build tool (not Vite-specific)
- Used as the default in NestJS, CRA, Angular (historically)
Jest Limitations
- Slow startup (TypeScript compiler, Babel transforms)
- ESM support remains awkward (requires
--experimental-vm-modules) - No HMR in watch mode (re-runs all tests)
- Significant memory usage for large test suites
Vitest
Package: vitest
Weekly downloads: 6M
GitHub stars: 13K
Creator: Anthony Fu / Vite team
Vitest is built on Vite's infrastructure. It uses the same module graph as Vite, enabling HMR for tests — changed tests re-run in milliseconds, not seconds.
Installation
npm install -D vitest
# For React tests:
npm install -D @testing-library/react @testing-library/jest-dom @vitejs/plugin-react jsdom
Configuration
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom', // or 'node', 'happy-dom'
globals: true, // No need to import describe/it/expect
setupFiles: ['./src/test/setup.ts'],
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
alias: {
'@': new URL('./src', import.meta.url).pathname,
},
},
});
Basic Test (Identical to Jest)
// user.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
// Vitest mock — same API as Jest
vi.mock('./user.repository');
describe('UserService', () => {
let service: UserService;
let mockRepo: ReturnType<typeof vi.mocked<typeof UserRepository>>;
beforeEach(() => {
vi.clearAllMocks();
// Same pattern as Jest
});
it('returns user by id', async () => {
vi.mocked(mockRepo.findById).mockResolvedValue({
id: '123',
name: 'Alice',
email: 'alice@example.com',
});
const user = await service.getUser('123');
expect(user.name).toBe('Alice');
});
});
The test code is almost identical to Jest — migration is usually a matter of renaming jest. to vi..
Vitest HMR Watch Mode
vitest --watch
# Single file changes: re-runs only affected tests in ~40ms
# Full re-runs only when configuration changes
This is the key Vitest advantage: if you change user.service.ts, Vitest knows exactly which tests import it and re-runs only those — without restarting the entire test runner.
Vitest Native ESM
// No --experimental-vm-modules flag needed
// ESM imports just work:
import { something } from './my-esm-module.mjs';
Vitest Browser Mode
// vitest.config.ts
export default defineConfig({
test: {
browser: {
enabled: true,
provider: 'playwright', // or 'webdriverio'
name: 'chromium',
},
},
});
Run tests in a real browser (not jsdom) — same API, real rendering engine.
Vitest Strengths
- 10-20x faster than Jest in watch mode (HMR)
- Native ESM and TypeScript (no transpilation config)
- Jest-compatible API (minimal migration effort)
- Vite-based: shares config with your Vite app
- Browser mode for real browser testing
- Excellent IntelliSense and VS Code integration
Vitest Limitations
- Vite-centric: less natural for non-Vite projects (webpack, Rollup, etc.)
- Smaller ecosystem than Jest (some Jest-specific plugins don't work)
- Browser mode is newer and less documented than Jest's jsdom
Bun Test
Part of: Bun runtime (bun.sh) API: Jest-compatible No npm install required
Bun's built-in test runner is the fastest option — written in Zig, it runs 15x faster than Jest on equivalent suites.
No Installation
# bun test is built-in — no package.json changes:
bun test
# Watch mode:
bun test --watch
# With coverage:
bun test --coverage
Writing Tests (Identical API)
// user.service.test.ts
import { describe, it, expect, beforeEach, mock } from 'bun:test';
import { UserService } from './user.service';
// Bun's mock function (similar to jest.fn())
const mockFindById = mock();
describe('UserService', () => {
beforeEach(() => {
mockFindById.mockClear();
});
it('returns user by id', async () => {
mockFindById.mockResolvedValue({
id: '123',
name: 'Alice',
email: 'alice@example.com',
});
// ... test code
});
});
Bun Test Snapshot Testing
import { test, expect } from 'bun:test';
test('renders correctly', () => {
const output = render(<Button>Click</Button>);
expect(output).toMatchSnapshot();
// Creates __snapshots__/button.test.tsx.snap
});
Coverage
bun test --coverage
# Output:
# | File | Stmts | Branch | Funcs | Lines |
# | user.service.ts | 95.2% | 88.5% | 100% | 95.2% |
Built-in V8 coverage — no @vitest/coverage-v8 or istanbul needed.
Bun Test Limitations
- Requires Bun runtime (can't run on Node.js)
- Not all Jest plugins work (jest-axe, Enzyme, etc.)
- Less mature ecosystem than Jest/Vitest
- Module mocking is less ergonomic than Jest/Vitest
Migration: Jest → Vitest
Most Jest tests migrate with minimal changes:
npm uninstall jest ts-jest @types/jest babel-jest
npm install -D vitest @vitest/coverage-v8
// Before (Jest):
import { jest } from '@jest/globals';
jest.mock('./module');
const mock = jest.fn();
// After (Vitest):
import { vi } from 'vitest';
vi.mock('./module');
const mock = vi.fn();
In vitest.config.ts, set globals: true to avoid importing describe, it, expect in every test file — maintaining Jest compatibility.
Feature Comparison
| Feature | Jest | Vitest | Bun test |
|---|---|---|---|
| Weekly downloads | 55M | 6M | N/A (built-in) |
| Startup time | ~1.2s | ~0.9s | ~0.08s |
| Watch mode speed | ~800ms | ~40ms (HMR) | ~50ms |
| TypeScript support | Via ts-jest/Babel | Native (Vite) | Native (Bun) |
| ESM support | Awkward | Native | Native |
| Jest API compat | Native | Yes | Mostly |
| Browser mode | No | Yes (Playwright) | No |
| Coverage | Yes | Yes (v8/istanbul) | Yes (V8) |
| Snapshot testing | Yes | Yes | Yes |
| Requires Vite | No | Recommended | No |
| Requires Bun | No | No | Yes |
Ecosystem & Community
Jest's community is the largest by far, built over nearly a decade of being the de-facto standard for JavaScript testing. The @testing-library family (React Testing Library, Vue Testing Library, etc.) was designed and primarily tested with Jest, and most component testing tutorials assume Jest. The jest-circus runner (default since Jest 27) improved parallelism, but the core architecture remains JavaScript-based and inherently slower than compiled alternatives.
Vitest's community has grown rapidly since its 2021 launch. Anthony Fu's prolific open-source presence and the broader Vite ecosystem's momentum have driven adoption. The Vitest team has an explicit goal of Jest compatibility — they regularly accept pull requests that close Jest API gaps. The community around testing in Vite-based projects has consolidated on Vitest as the obvious choice, and framework authors building on Vite (Nuxt, SvelteKit, Astro) recommend Vitest in their documentation.
Bun's test runner community is the smallest of the three but benefits from Bun's overall developer enthusiasm. The Bun team has been responsive to testing-specific requests, and the API compatibility with Jest is intentional — they want Bun test to be a drop-in for developers migrating to Bun as a runtime. The main constraint is that testing library plugins written for Jest or Vitest don't automatically work with Bun's test runner.
Real-World Adoption
Jest remains the dominant test runner for production applications by installed base. NestJS ships with Jest configured by default, and NestJS is used by thousands of enterprise Node.js backends. Angular has historically used Jest, and the Angular testing community has extensive documentation and tooling around it. Any team that started their testing strategy before 2022 is likely on Jest, and many have no reason to migrate.
Vitest has become the default for the Vite-native ecosystem. Every new framework built on Vite either recommends or defaults to Vitest. SvelteKit, Astro, Nuxt 3, and most modern Vue applications use Vitest. For teams starting new projects in 2026, the question is typically "Jest or Vitest?" and the growing answer is Vitest — unless there's a specific Jest-only dependency.
Bun's test runner has attracted the "developer tools" segment — libraries, CLIs, and utilities that prioritize testing speed above ecosystem breadth. Teams building developer tools often have many small, fast unit tests and care deeply about the feedback loop during development. Bun test's 0.08s startup time makes it genuinely useful here in a way that Jest and Vitest's sub-second starts still don't match.
Developer Experience Deep Dive
Jest's developer experience is mature and well-documented. The VS Code Jest extension provides inline test results, run-on-save, and debugging support. The --watch mode with interactive filtering (press t to filter by test name) is well-designed. The main friction in Jest's DX is TypeScript configuration — getting ts-jest configured correctly, handling path aliases in jest.config.json, and managing the different module resolution between test and production code requires knowledge that isn't always obvious from documentation.
Vitest's developer experience benefits from sharing Vite's infrastructure. Configuration is in vitest.config.ts using the same format as vite.config.ts, and path aliases defined for the application automatically work in tests. The VS Code Vitest extension provides similar inline test result feedback to Jest's extension. TypeScript "just works" — no babel, no ts-jest, no separate transpiler configuration. This zero-friction TypeScript story is Vitest's biggest DX advantage over Jest.
Bun's test runner DX is the simplest because there's almost nothing to configure. Run bun test and tests run. Coverage runs with --coverage. Watch mode runs with --watch. The absence of configuration is a feature for projects that don't need customization, but it becomes a limitation when you need to configure test environments, custom reporters, or module resolution beyond Bun's defaults.
Performance & Benchmarks
The speed differences between the three runners are real and consistent across project sizes:
For small to medium test suites (under 100 tests), all three runners feel fast enough that the difference is academic. At 1,000+ tests across 200+ files, the differences become operationally significant. Jest's 1.2s startup plus test execution time means waiting 5-10 seconds for a full run; Vitest's HMR means only running the 3-5 tests affected by a single file change in under 100ms.
Bun's advantage is most pronounced on initial startup — the 0.08s figure is for the test runner itself, not the total test execution time. For suites where most time is spent in test setup and teardown (database fixtures, mock setup, etc.), the runtime cost of each test is similar across all three runners. Bun's advantage compounds when running many small, fast tests — utility libraries, parsers, data transformation code.
Choosing Your Test Runner in 2026
Use Vitest if:
- Your project uses Vite (Next.js via Vite, Nuxt, Astro, SvelteKit, most modern setups)
- You want a fast Jest drop-in with minimal migration
- TypeScript and ESM without transpilation config is important
- You want browser mode testing
Use Jest if:
- Large existing Jest codebase with complex mocking patterns
- You use Jest-specific plugins (Enzyme, jest-axe, jest-fetch-mock)
- NestJS, CRA, or other frameworks that configure Jest by default
- Non-Vite build setup where Vitest integration is awkward
Use Bun test if:
- Your entire project runs on Bun (not just Node.js)
- Raw speed is the priority and Jest API compatibility is close enough
- You want a zero-dependency test runner for simple utilities
Final Verdict 2026
For new TypeScript projects in 2026, Vitest is the default choice. The migration cost from Jest is low (rename jest. to vi.), the performance improvement in watch mode is meaningful, and the TypeScript integration requires no configuration. If your project is already on Vite or a Vite-based framework, Vitest is the obvious answer.
Bun test is compelling for teams already running on Bun as their runtime. If you've adopted Bun for its speed and Node.js compatibility is not a concern, using Bun's built-in test runner is consistent with that choice and delivers the fastest possible test execution.
Jest remains the right choice for existing codebases with significant investment in Jest-specific tooling, and for teams on NestJS or other frameworks that configure Jest by default. The migration to Vitest is low-risk but not zero-risk — validate your test suite thoroughly before committing.
Compare testing library trends on PkgPulse.
Compare Bun Test, Vitest, and Jest package health on PkgPulse.
Related: Best JavaScript testing frameworks in 2026 · pnpm vs Bun vs npm package manager comparison · Bun test vs Vitest vs Jest benchmark deep dive