oven-sh/bun
Test runner
Active contributors: Jarred Sumner, Dylan Conway
bun test is a Jest-compatible test runner built into the runtime. The dispatch entry is src/cli/test_command.zig (~103 KB). It shares the runtime's VM, parser, and module loader.
Purpose
- Discover
*.test.ts/*.test.tsx/*.test.js/... files. - Spawn one process per test file (parallel) and run each file's
describe/it/test/expecthooks against the runtime. - Collect results, render output, and emit JUnit / GitHub Actions /
--reporter=tapformats. - Support snapshot tests (
toMatchSnapshot,toMatchInlineSnapshot), code coverage, watch mode, lifecycle hooks (beforeAll/beforeEach/...). - Handle
bun test --filter,bun test --bail,bun test --rerun-each,bun test --coverage.
Directory layout
src/
├── cli/test_command.zig # the CLI entry, ~103 KB
├── cli/test/
│ ├── ChangedFilesFilter.zig # --changed (only run files affected by changes)
│ ├── ParallelRunner.zig # process-per-file controller
│ ├── Scanner.zig # discover test files
│ └── parallel/ # per-worker helpers
└── bun.js/test/ # `bun:test` JS API surface (expect, describe, it, ...)
├── Bun-test bindings...
└── snapshot machineryThe actual expect implementation, matchers, snapshot serialiser, and timer/clock mocks all live under src/bun.js/test/ and are exposed as the bun:test hardcoded module.
Key abstractions
| Type | File | Purpose |
|---|---|---|
TestCommand |
src/cli/test_command.zig |
The argv parser and orchestrator. Decides how many workers to spawn and which files go to each. |
Scanner |
src/cli/test/Scanner.zig |
Walks the working directory, applies test-file globs from bunfig.toml, and emits a list. |
ParallelRunner |
src/cli/test/ParallelRunner.zig |
Manages the pool of child processes. Each child runs one file end-to-end. |
ChangedFilesFilter |
src/cli/test/ChangedFilesFilter.zig |
Implements --changed: looks at the git working tree and limits scope. |
Jest API surface |
src/bun.js/test/*.zig and src/js/bun/jest.ts |
The user-facing expect, describe, it, mock, spyOn. |
How a test run works
sequenceDiagram
participant CLI as src/cli/test_command.zig
participant Scan as Scanner.zig
participant Runner as ParallelRunner.zig
participant Child as bun-test child
participant VM as VirtualMachine.zig
participant API as bun:test API
CLI->>Scan: glob test files
Scan-->>CLI: list of files
CLI->>Runner: dispatch with concurrency
par for each file
Runner->>Child: spawn `bun --bun-test <file>`
Child->>VM: bring up runtime
VM->>API: load bun:test module
VM->>VM: evaluate test file
API-->>Runner: streaming results (TAP-ish)
Child-->>Runner: exit code
end
Runner->>CLI: aggregate
CLI-->>User: render summaryParallelism is via OS processes, not threads. The default concurrency is os.availableParallelism() / 2. A failed worker doesn't tear down siblings.
The child process loads bun:test (a hardcoded module) which sets up globalThis.describe, globalThis.it, globalThis.expect, and friends. Test discovery within the file happens at evaluation time — it("name", fn) registers a test with the active describe block.
Snapshots
expect(x).toMatchSnapshot() writes serialised values to __snapshots__/<file>.snap. Inline snapshots (toMatchInlineSnapshot) rewrite the source file directly when --update-snapshots is passed. The serialiser is in src/bun.js/test/snapshot.zig plus pretty-printer hooks in src/bun.js/ConsoleObject.zig.
normalizeBunSnapshot(stdout) from test/harness.ts is the recommended helper: it strips ANSI, line numbers, and tempdir paths so snapshots stay stable across runs.
Coverage
bun test --coverage enables JSC's coverage profiler. Bun samples function/branch coverage and writes coverage/lcov.info (or text/JSON depending on flags). The integration is in src/cli/test_command.zig (look for enable_coverage) and the JSC binding is src/bun.js/bindings/BunCoverage.cpp.
Mocks and timers
mock(fn), mock.module(), spyOn(obj, "method"), vi.useFakeTimers() analogues are implemented in src/bun.js/test/. Fake timers reuse the runtime's Timer.zig machinery — they advance the timer treap manually instead of waiting for the OS.
Watch mode
bun test --watch plugs the file watcher (src/Watcher.zig) into the runner. On change, the runner re-runs only test files whose dependency graph touches the changed file. The graph is computed from the runtime's module loader cache.
CI integrations
| Reporter | Flag |
|---|---|
| TAP | --reporter=tap |
| JUnit XML | --reporter=junit --reporter-outfile=results.xml |
| GitHub Actions annotations | auto-detected when GITHUB_ACTIONS=1 |
| JSON streaming | --reporter=json |
The bundled CI helpers in package.json (bun run ci:errors, bun run ci:status) wrap BuildKite specifically.
bunfig.toml options
Test discovery and execution are configured under [test] in bunfig.toml. Common keys:
[test]
preload = ["./test/setup.ts"]
coverage = true
coverageThreshold = { line = 0.8, function = 0.8 }
coverageReporter = ["lcov", "text"]Schema: src/bunfig.zig parses TOML and exposes the structured options into Arguments.zig.
Test conventions in this repo
The project's own tests under test/ follow rules documented in test/CLAUDE.md and CLAUDE.md:
- Tests live next to the API they cover. A
Bun.servebug goes intest/js/bun/http/serve.test.ts. - Use
tempDirfromtest/harness.ts, neverfs.mkdtempSync. - Use
port: 0and let the OS pick a port. - Use
bunExe()andbunEnvfromtest/harness.tsto spawn child processes — this picks upUSE_SYSTEM_BUN=1for regression tests. expect(stdout).toBe(...)beforeexpect(exitCode).toBe(0). Better failure output.- Verify a regression test fails on
mainwithUSE_SYSTEM_BUN=1 bun test <file>and passes on the fix branch withbun bd test <file>.
Integration points
- Runtime —
bun testisbun runwithbun:testpreloaded and the file-discovery machinery added. See Runtime. - Module loader — Test file resolution uses the same Module resolution path.
- Snapshot serialiser — Borrows from
src/bun.js/ConsoleObject.zig's formatter. - Watcher — Watch mode uses
src/Watcher.zig.
Entry points for modification
- To add a CLI flag, edit
src/cli/test_command.zigandsrc/cli/Arguments.zig. - To add a matcher (
expect(...).toBeFoo()), look atsrc/bun.js/test/expect.zigor the matcher TS file undersrc/js/bun/. - To change snapshot serialisation, edit
src/bun.js/test/snapshot.zigand the inspector path inConsoleObject.zig. - To support a new reporter, add a printer under
src/cli/test/and wire it throughParallelRunner.zig.
Key source files
| File | Purpose |
|---|---|
src/cli/test_command.zig |
CLI entry, runner glue. |
src/cli/test/ParallelRunner.zig |
Parallel worker pool. |
src/cli/test/Scanner.zig |
Test file discovery. |
src/bun.js/test/ |
bun:test JS API and matchers. |
test/harness.ts |
Test helpers (tempDir, bunExe, normalizeBunSnapshot). |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.