Vitest vs Bun test vs Jest: test runner 2026
3 test runner JavaScript. Vitest = ESM-first compat, Bun = native+cepat, Jest = legacy de facto. Saya pakai keduanya, verdict tergantung runtime.
TL;DR
- Vitest: Recommended untuk Node project, ekosistem Jest-compatible
- Bun test: Recommended untuk Bun runtime project, native + super cepat
- Jest: Skip untuk project baru, only maintain legacy
Konteks
3 project saya 2024-2026:
- SaaS billing fotograf (Node + Hono): Vitest
- SaaS klinik dental (Bun + Hono): Bun test
- Internal tool Laravel + React: Vitest (untuk React frontend)
Performance benchmark
Same test suite: 120 test cases (unit + integration), TypeScript, mock fetch.
| Runner | Cold run | Hot run (watch) |
|---|---|---|
| Jest | 8.4s | 2.8s |
| Vitest | 2.1s | 0.4s |
| Bun test | 0.9s | 0.2s |
Bun test paling cepat (native). Vitest signifikan lebih cepat dari Jest karena ESM-first + Vite-based.
Vitest
Pro
- Jest-compatible API: 95% Jest test bisa run di Vitest tanpa change
- ESM-first: tidak butuh
@babel/preset-env+ CommonJS dance - Vite ecosystem: kalau Anda pakai Vite (or Astro), config shared
- Watch mode: instant re-run via Vite HMR
- UI mode: visual test runner (run via
vitest --ui) - Active development: weekly release, fix bug fast
Con
- Bundle size lebih besar dari Bun test
- TypeScript support OK tapi butuh tsconfig sync
- Mock API slightly less elegant dari Bun test
Example
import { describe, it, expect, vi } from 'vitest';
import { calculateBill } from './bill';
describe('calculateBill', () => {
it('compute tax correctly', () => {
expect(calculateBill(100, 0.11)).toBe(111);
});
it('handles network error', async () => {
const fetch = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('Network'));
await expect(getRates()).rejects.toThrow('Network');
});
});
Bun test
Pro
- Native ke Bun runtime: tidak butuh dependency tambahan
- Paling cepat: 2-3x faster dari Vitest
- Same syntax as Jest/Vitest: minimal learning curve
- Built-in coverage (via
bun test --coverage) - Snapshot test built-in
Con
- Bun-only: tidak run di Node CI yang tidak install Bun
- Ecosystem kecil: less third-party reporter, plugin, integration
- TypeScript config kadang issue: subtle behavior differences dari tsc
Example
import { describe, it, expect, mock } from 'bun:test';
import { calculateBill } from './bill';
describe('calculateBill', () => {
it('compute tax correctly', () => {
expect(calculateBill(100, 0.11)).toBe(111);
});
it('handles network error', async () => {
const fetch = mock(() => Promise.reject(new Error('Network')));
globalThis.fetch = fetch;
await expect(getRates()).rejects.toThrow('Network');
});
});
Hampir identical dengan Vitest. Migration trivial.
Jest
Pro
- Most mature: 10+ tahun, paling banyak ecosystem
- Familiar: most React tutorial pakai Jest
- Stable: bug rare, behavior predictable
Con
- Slow di 2026: 4-10x lebih lambat dari Vitest/Bun
- CommonJS-first: butuh transform untuk ESM modern code
- Config kompleks: babel + jest.config.js + transformers
- Stagnant innovation: feature baru release lambat
Migration
Jest → Vitest
bun remove jest @types/jest babel-jest jest-environment-jsdom
bun add -d vitest @vitest/ui happy-dom
Update package.json:
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest --coverage"
}
}
Create vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'happy-dom', // atau 'node' atau 'jsdom'
globals: true,
},
});
Update test files: change from 'jest' → from 'vitest'. Most other API same.
Time: 1-2 jam untuk 100-200 test file.
Vitest → Bun test
# Remove vitest
bun remove vitest @vitest/ui happy-dom
# Update import
# from 'vitest' → from 'bun:test'
# Update test script
# "test": "vitest" → "test": "bun test"
Bun test cover most Vitest API. Some edge case (custom matchers, advanced mock) butuh adjust.
Time: 30-60 menit untuk simple suite, 2-4 jam untuk complex.
Common pain point
Mocking external module
Jest: jest.mock('./module', () => ({ ... })). Powerful but complex.
Vitest: vi.mock('./module', () => ({ ... })). Similar to Jest.
Bun test: mock.module('./module', () => ({ ... })). Newer API, occasional bug.
Untuk complex mocking: Vitest lebih mature dari Bun test.
TypeScript path alias
Vitest read tsconfig automatically. Bun test sometimes butuh manual path setup di bunfig.toml.
CI integration
GitHub Actions:
# Vitest
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: bun install && bun run test
# Bun test
- uses: oven-sh/setup-bun@v2
- run: bun install && bun test
Both straightforward.
Decision matrix
| Project type | Recommended |
|---|---|
| Bun runtime (Hono, Elysia) | Bun test |
| Node runtime (Express, NestJS) | Vitest |
| Vite/Astro project | Vitest |
| Legacy Jest project | Vitest (migrate) atau Jest (stay) |
| Performance-critical CI | Bun test |
Konteks Indonesia
Untuk SMB Indonesia 2026:
- Most project saya pakai Bun runtime → Bun test default
- Untuk klien dengan team Node existing → Vitest (less migration friction)
- Jangan default Jest untuk project baru (legacy choice di 2026)
Yang surprising
Bun test cepat banget di CI. Saya migrate 1 project klien dari Vitest ke Bun test → CI time turun dari 4.2 menit ke 1.8 menit. Saving signifikan untuk team yang push 20+ PR per minggu.
Vitest watch mode under Vite HMR sangat smooth — test re-run dalam 100-300ms saat file change. Untuk TDD workflow, Vitest still excellent.
Verdict
Recommended:
- Bun test untuk Bun runtime project (default jika Anda commit ke Bun)
- Vitest untuk Node runtime atau Vite/Astro project
Skip:
- Jest untuk new project (legacy choice)
Pilih based on runtime + ecosystem fit. Migration cost rendah, jadi tidak perlu over-think.
Ditulis oleh Asti Larasati