Bun Test Runner: A Zero‑Config, Fast Jest‑Compatible Alternative
Use Bun’s built‑in test runner to replace Jest or Vitest without configuration. Discover how it auto‑detects tests, provides Jest‑style globals, supports snapshots, watch mode, and coverage, and learn migration caveats and performance gains.
20 Aug 2025, 04:24 UTC

Why switch to Bun’s test runner?
When you run bun test you get a Jest‑compatible API, no configuration files, and native TypeScript/JSX transpilation. The result is a test suite that starts in 50 ms, runs 2‑10× faster than Jest on large projects, and consumes less memory because Bun avoids a separate test framework and Node’s module resolution overhead.
Getting started – a minimal example
Create a tiny repo to see the runner in action:
mkdir bun-test-demo
cd bun-test-demo
# A small function to test
cat > math.ts <<'EOF'
export const add = (a: number, b: number) => a + b
EOF
# A test file that uses Bun’s globals
cat > math.test.ts <<'EOF'
import { describe, it, expect } from "bun:test"
import { add } from "./math"
describe("add", () => {
it("sums two numbers", () => {
expect(add(1, 2)).toBe(3)
})
})
EOF
# Run the test
bun test
With no tsconfig.json, jest.config.js, or Babel setup, the test passes in under 200 ms on a modern machine.
How it works – the mechanism
Discovery: bun test looks for files matching *.test.{ts,tsx,js,jsx} in the current directory and subfolders. It does not require a config file; all settings are passed as command‑line flags.
Transpilation: Bun’s runtime compiles TypeScript and JSX on the fly using the same engine that powers the rest of the runtime. No separate Babel step is needed.
Globals: The runner injects Jest‑style globals (describe, it, expect, beforeEach, vi.fn, etc.) from bun:test. You can import them explicitly as shown above or rely on the globals if you prefer.
Mocking: Use vi.mock or mock.module for ESM imports. jest.mock hoisting is not replicated; mocks must be declared at the top level of the test file.
Key features demonstrated
Snapshot testing
Snapshots are stored next to the test file in a __snapshots__ folder. Update them with bun test -u.
import { describe, it, expect } from "bun:test"
describe("snapshot", () => {
it("matches object", () => {
expect({ a: 1, b: 2 }).toMatchSnapshot()
})
})
Watch mode
Run bun test --watch to automatically restart only the changed test files. Restart times are typically <100 ms, compared to several seconds with Jest.
Coverage
Built‑in coverage uses V8’s native instrumentation. Run:
bun test --coverage --coverage-out-dir=./coverage
The reports are in lcov, text, or JSON format and can be consumed by CI tools.
Parallelism and isolation
By default each test file runs in its own worker process, preventing global state leaks. Debugging a single file can be done with --no-worker.
Performance numbers (typical)
| Feature | Bun (ms) | Jest (ms) |
|---|---|---|
| Cold start | ≈50 | ≈200–300 |
| Large suite (500 tests) | ≈1 200 | ≈5 000–7 000 |
| Watch mode restart | ≈80 | ≈2 000 |
These numbers vary with hardware; the trend is consistent: Bun is faster and lighter.
Migrating from Jest/Vitest
- Add
"test": "bun test"topackage.json. - Remove
jest.config.js,babel.config.js, and@types/jestfrom dependencies. - Replace
jest.mock('module')withvi.mock('module')ormock.module('module', () => ({}))at the top level. - For DOM‑dependent tests, install
happy-domorjsdomand setglobalThis.documentin asetupTests.tsfile. - Run
bun testand fix any failing mocks or global state issues.
Common pitfalls and limits
- Mock hoisting:
jest.mockhoisting is not supported. All mocks must be declared before any imports that use them. - Dynamic mocks:
vi.mock()called inside test bodies is ignored. Declare mocks at the top of the file. - CommonJS requires:
mock.moduleworks with ESM imports. Forrequire()mocks, usebun:test’s interception API. - DOM APIs: Bun does not ship a JSDOM environment. Install
happy-domorjsdomand configureglobalThis.documentmanually or via@testing-library/bun. - CI resource limits: On containers with limited CPUs, reduce parallel jobs with
--jobs=2to avoid OOM kills. - Coverage exclusions:
node_modulesare excluded by default. Include internal packages with--coverage-include="**/my‑pkg/**".
Practical verification steps
- Check the Bun version:
bun --versionshould be ≥1.1.0. - Run the minimal repo test:
bun testshould pass in <200 ms. - Verify watch mode:
bun test --watch, edit a test file, and confirm it reruns instantly. - Generate coverage:
bun test --coverage --coverage-out-dir=./covand inspectcov/lcov.info. - Check snapshot flow: add
expect({a:1}).toMatchSnapshot(), runbun test -u, and inspect__snapshots__/math.test.ts.snap. - Test a Jest suite: copy a small Jest test file, remove Jest configs, run
bun test, and note any mock hoisting failures.
Conclusion
Bun’s test runner delivers a near‑drop‑in replacement for Jest/Vitest with zero configuration, native TypeScript support, and significant performance gains. The main migration hurdles are mock hoisting, DOM setup, and CommonJS require handling. For teams looking to reduce their test runtime and simplify their toolchain, Bun’s built‑in runner is a compelling choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.