How to Write and Run Tests Using Bun's Built‑in Test Runner
Bun provides a Jest‑compatible test runner accessible via the bun test CLI command that supports TypeScript, snapshots, mocks, and watch mode without external dependencies.
The oven-sh/bun repository ships a fast, integrated test framework that mirrors the Node.js node:test API while adding Bun‑specific optimizations. You can write tests using .test.{ts,js,tsx,jsx,mjs,cjs} files and execute them through the bun binary, leveraging the runtime's native TypeScript compilation and ESM/CJS support.
Writing Tests with the bun:test API
The core test implementation lives in src/js/node/test.ts, which exports the global test object along with lifecycle hooks and snapshot utilities. This module registers tests with the internal Bun runner and exposes a TestContext class providing introspection methods like ctx.signal, ctx.name, and ctx.diagnostic().
Importing the Test Module
While test and describe are available globally, you should import expect explicitly from bun:test to access Jest‑compatible matchers:
import { expect, test, describe, beforeEach, afterEach } from "bun:test";
Basic Test Structure
Create a file ending with .test.ts (or .test.js, .test.tsx, etc.) and export test cases using the test() function:
import { expect, test } from "bun:test";
test("adds two numbers", () => {
expect(1 + 2).toBe(3);
});
test("async operations work", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
As implemented in src/js/node/test.ts, the test function accepts an optional options object supporting timeout, skip, only, and todo flags.
Grouping Tests with describe
Organize related tests using describe() (aliased as test.describe and test.suite in the source):
import { expect, test, describe, beforeEach } from "bun:test";
describe("Array utilities", () => {
let arr: number[];
beforeEach(() => {
arr = [1, 2, 3];
});
test("pop removes last element", () => {
const last = arr.pop();
expect(last).toBe(3);
expect(arr).toEqual([1, 2]);
});
test.todo("implement shuffle");
});
Lifecycle Hooks
The API exposes before, after, beforeEach, and afterEach hooks that delegate to the underlying Bun runner:
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
beforeAll(() => {
// Runs once before all tests in the file
});
beforeEach(() => {
// Runs before each test
});
Advanced Testing Features
Skipping and Isolating Tests
Control test execution using modifiers on the test function:
test.skip()– Excludes the test from execution and prints "skipped".test.only()– Runs only this test and bails early (isolation mode).test.todo()– Marks a test as planned but not yet implemented.
test.skip("slow integration test", async () => {
// This test will not run
});
test.only("focus mode", () => {
// Only this test executes
});
Snapshot Testing
Bun includes built‑in snapshot support via expect().toMatchSnapshot(). The first run stores data in a __snapshots__ directory, while subsequent runs compare against these stored values:
test("output snapshot", () => {
expect({ user: "alice", id: 123 }).toMatchSnapshot();
});
The test.snapshot export in src/js/node/test.ts provides setDefaultSnapshotSerializer and setResolveSnapshotPath for customizing snapshot behavior.
Mock Functions
Use Bun's Jest‑compatible mock API to spy on function calls:
import { mock, test, expect } from "bun:test";
test("mock function tracking", () => {
const fn = mock(() => "result");
fn("arg1");
fn("arg2");
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith("arg1");
});
Timeouts and Retries
Configure individual test timeouts using the options parameter or global CLI flags:
// 5 second timeout for this specific test
test("slow operation", async () => {
// ...
}, { timeout: 5_000 });
For flaky tests, use the --retries CLI flag to automatically re‑run failed assertions.
Running Tests from the CLI
Basic Commands
Execute all discovered test files (matching *.test.* patterns) with:
bun test
Run a specific file or directory:
bun test src/utils/string.test.ts
bun test src/lib/
Watch Mode and Coverage
Enable file watching to automatically re‑run tests on changes:
bun test --watch
Generate coverage reports in LCOV format:
bun test --coverage
Additional useful flags include:
--bail– Stop execution after the first failure.--filter <pattern>– Run only tests matching the description pattern.--timeout <ms>– Set a global timeout for all tests.
Debugging with Source Changes
When modifying the Bun runtime source code, you must use the debug build (bun bd) rather than the system‑installed binary. According to AGENTS.md, compile and test with:
# Build the debug binary
bun bd
# Run tests using the debug build
bun bd test
Running bun test without the debug prefix would use your globally installed Bun version, missing any local modifications to src/js/node/test.ts or the runtime.
Summary
- Test files use the
.test.{ts,js,tsx,jsx,mjs,cjs}extension and are automatically discovered bybun test. - The
bun:testmodule insrc/js/node/test.tsprovidestest,describe, lifecycle hooks, and snapshot utilities with a Jest‑compatible API. - CLI execution supports
--watchfor continuous testing,--coveragefor LCOV reports, and--bailfor early exit on failure. - Development workflow requires
bun bd testwhen modifying the Bun runtime source to ensure changes are compiled into the test binary.
Frequently Asked Questions
Does Bun's test runner require Jest to be installed?
No. Bun's test runner is implemented natively in the runtime and does not require jest, vitest, or any other external testing framework. The API is Jest‑compatible, allowing migration of existing test suites without configuration changes, but all functionality is built into the bun binary itself.
How do I run a single test file or a specific test?
Run a specific file by passing its path to bun test: bun test path/to/file.test.ts. To run a specific test within a file, use test.only() in your source code or the --filter flag with a pattern matching the test description: bun test --filter "adds two numbers".
What file extensions does Bun recognize for test files?
Bun automatically discovers files ending with .test.ts, .test.js, .test.tsx, .test.jsx, .test.mjs, and .test.cjs. You can also pass directories to bun test, and it will recursively search for matching test files within those paths.
How do I debug why a test is failing in the Bun runtime source?
When working on the Bun runtime itself (modifying src/js/node/test.ts or related source files), use bun bd to compile a debug build, then execute bun bd test. Using the standard bun test command would invoke your system‑installed Bun release, which would not include your local source modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →