Open-Source Wikis

/

Bun

/

How to contribute

/

Testing

oven-sh/bun

Testing

Bun's tests live in test/. They run on bun:test, a Jest-compatible runner that's part of Bun itself. The runner is what you ship; the tests are what you must trust.

Layout

test/
├── js/
│   ├── bun/        # Bun-specific APIs (http, crypto, ffi, shell, ...)
│   ├── node/       # Node compatibility (node:fs, node:child_process, ...)
│   └── web/        # Web APIs (fetch, WebSocket, streams, ...)
├── cli/            # CLI commands (install, run, test, ...)
├── bundler/        # Bundler / transpiler. Use itBundled.
├── integration/    # End-to-end integration tests (real workflows).
├── napi/           # N-API
├── v8/             # V8 C++ API compatibility
├── regression/
│   └── issue/      # /issueNumber.test.ts — true regressions only
└── harness.ts      # Shared helpers (bunEnv, bunExe, tempDir, normalizeBunSnapshot)

Default rule: extend the existing file

Add your test to the existing test file for the code you're changing. Do not create a new file. — CLAUDE.md

A Bun.serve bug → test/js/bun/http/serve.test.ts. A fetch() bug → test/js/web/fetch/fetch.test.ts. A bun install bug → an existing file under test/cli/install/. Tests next to related coverage stay discoverable.

Exception: test/regression/issue/

Use test/regression/issue/<issueNumber>.test.ts only when:

  • There's a real GitHub issue number (no placeholders).
  • The behaviour is a true regression — worked in a previous release, then broke.

If the behaviour was never correct, it's a bug not a regression — extend the existing file instead.

Single-file tests: prefer -e

import { expect, test } from 'bun:test';
import { bunEnv, bunExe, normalizeBunSnapshot } from 'harness';

test('(single-file test) my feature', async () => {
  await using proc = Bun.spawn({
    cmd: [bunExe(), '-e', "console.log('Hello, world!')"],
    env: bunEnv,
  });

  const [stdout, stderr, exitCode] = await Promise.all([
    proc.stdout.text(),
    proc.stderr.text(),
    proc.exited,
  ]);

  expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`"Hello, world!"`);
  expect(exitCode).toBe(0);
});

Multi-file tests: prefer tempDir

import { expect, test } from 'bun:test';
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from 'harness';

test('(multi-file test) my feature', async () => {
  using dir = tempDir('test-prefix', {
    'index.js': `import { foo } from "./foo.ts"; foo();`,
    'foo.ts': `export function foo() { console.log("foo"); }`,
  });

  await using proc = Bun.spawn({
    cmd: [bunExe(), 'index.js'],
    env: bunEnv,
    cwd: String(dir),
    stderr: 'pipe',
  });

  const [stdout, stderr, exitCode] = await Promise.all([
    proc.stdout.text(),
    proc.stderr.text(),
    proc.exited,
  ]);

  expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"foo"`);
  expect(exitCode).toBe(0);
});

Hard rules

  • Always use port: 0. No hardcoded ports. No "random port" helpers — let the OS pick.
  • Use tempDir from "harness". Not tmpdirSync, not fs.mkdtempSync.
  • No setTimeout to "wait for" something. Await the actual condition.
  • expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). Better failure messages.
  • Use normalizeBunSnapshot for snapshot output. Strips paths, ANSI codes, version stamps.
  • Never assert "no panic in stderr". Those tests don't fail in CI even when they should.

Bundler tests: itBundled

import { itBundled } from './expectBundled';

itBundled('ssr/Imports', {
  files: {
    '/entry.ts': `import { x } from "./other"; console.log(x);`,
    '/other.ts': `export const x = 42;`,
  },
  outfile: '/out.js',
  run: { stdout: '42' },
});

itBundled snapshots the bundler output and runs it for behavioural verification.

Verification

A test is valid only if:

USE_SYSTEM_BUN=1 bun test <file>     # FAILS on main
bun bd test <file>                    # PASSES on your branch

If the test passes with USE_SYSTEM_BUN=1, you didn't actually fix the bug or didn't actually exercise the fix. Fix the test.

When to run the whole suite

The full suite is slow. CI does it for you. Locally:

  • Run the focused file(s).
  • If you touched something cross-cutting (the lexer, the resolver, the event loop), grep for related test files and run them.
  • For platform-specific changes, run bun run zig:check-all to compile-check Linux/macOS/Windows.

Special test infra

Helper Purpose
harness.ts bunEnv, bunExe, tempDir, normalizeBunSnapshot, gc, etc.
expectBundled Bundler input → expected output / behaviour.
inspector-protocol/ DevTools protocol fixtures.
internal/ Tests that prod internal-only behaviour (crash handler, OOM).

Test conventions reference

The authoritative reference is test/CLAUDE.md. Read it before writing tests in unfamiliar territory.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – Bun wiki | Factory