smol mode for memory savings
Enable smol = true in [test] section to reduce memory usage during test runs. Smol mode reduces memory by using less memory for the JavaScript heap, being more aggressive about garbage collection, and reducing buffer sizes where possible. Use it in memory-constrained environments like CI runners or for large test suites.
Test configuration in bunfig.toml
Configure bun test behavior by adding a [test] section to bunfig.toml. Options are placed under this section heading.
CLI options override configuration file
Command-line options always override configuration file settings. For example, bun test --coverage will enable coverage even if coverage = false is set in bunfig.toml.
root option for test discovery
The root option sets the directory Bun scans for tests, instead of scanning the project root. Set it to a directory like 'src' or 'tests' to limit test discovery to that location.
pathIgnorePatterns prevents test discovery
The pathIgnorePatterns option excludes files and directories from test discovery using glob patterns. Unlike coveragePathIgnorePatterns which only affects coverage reports, pathIgnorePatterns prevents matching paths from being discovered and run as tests. Bun prunes directories matching a pattern during scanning and never traverses their contents. It accepts a single pattern string or a list of patterns.
Environment variables via .env files
Set environment variables for tests with .env files which Bun loads from the project root automatically. For test-specific variables, create a .env.test file which bun test loads automatically.
Install settings inheritance by bun test
bun test inherits network and installation configuration from the [install] section of bunfig.toml, including registry, cafile, prefer, and exact settings. This matters if tests reach a private registry or trigger installs during the run.
seed option for reproducible random test order
The seed option specifies a seed for reproducible random test order. It requires randomize = true to be set. Provide a numeric seed value.
concurrentTestGlob for gradual concurrent migration
The concurrentTestGlob option runs test files matching a glob pattern with concurrent test execution enabled. Test files matching the pattern behave as if --concurrent was passed, so every test in those files runs concurrently. Use this to migrate a test suite to concurrent execution gradually or to run one kind of test concurrently while the rest stay sequential. The --concurrent CLI flag overrides this setting.
randomize option to identify test dependencies
Set randomize = true in [test] section to run tests in random order. This helps identify tests with hidden dependencies.
preload option for setup scripts
The preload option loads scripts before tests run. It accepts a list of file paths. This is equivalent to using --preload on the command line. Preload scripts can set up test databases, mock environment variables, and mock external dependencies using beforeAll/afterAll hooks and mock.module().
retry option for default retry count
The retry option sets the default retry count for all tests. Bun retries a failed test up to this many times. Per-test { retry: N } overrides this value. Default is 0 (no retries). The --retry CLI flag overrides this setting.
rerunEach option to identify flaky tests
Set rerunEach = N to re-run each test file multiple times. This helps identify flaky tests that fail intermittently.
pathIgnorePatterns CLI flag overrides config
Command-line --path-ignore-patterns flags override the bunfig.toml value entirely; the two are not merged.
Run a specific test file
To run a specific file, ensure the path starts with ./ or / to distinguish it from a filter name. For example: bun test ./test/specific-file.test.ts
Test name pattern matching includes parent describe blocks
When using --test-name-pattern, a test defined within nested describe blocks like describe('Math', () => { describe('operations', () => { test('should add correctly', () => {}) }) }) is matched against the string 'Math operations should add correctly'.
Change test root directory in bunfig.toml
Set the root directory where Bun searches for test files using the root option under [test] in bunfig.toml. For example: [test]\nroot = "src" scans for tests only in the src directory.
Directories and files excluded from test discovery
By default, bun test ignores node_modules directories, hidden directories (starting with a period), and files without JavaScript-like extensions.
Default test file discovery patterns
Bun test recursively searches the project directory for files matching these patterns: *.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, *_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, *.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, and *_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}.
Filter test files by substring match
Use positional arguments to filter which test files run: bun test <filter> <filter> ... Any test file with a path containing one of the filters runs. Filters are substring matches, not glob patterns. For example, 'bun test utils' matches files like src/utils/string.test.ts and lib/utils/array_test.js.
Filter tests by name with regex pattern
Use the -t or --test-name-pattern flag with a regex pattern to filter tests by name instead of file path. For example: bun test --test-name-pattern addition. The pattern matches against the test name prefixed with the labels of all its parent describe blocks, separated by spaces.
Test execution order
Test files run sequentially, or across worker processes with the --parallel flag. Within each file, tests run sequentially in definition order.
TypeScript DOM types in test files
To resolve TypeScript 'Cannot find name document' errors when testing DOM, add the triple-slash directive '/// <reference lib="dom" />' at the top of test files to load types for document and other browser APIs.
happy-dom for headless DOM testing
Bun's test runner works with happy-dom for headless testing of frontend code and components. happy-dom implements a complete set of HTML and DOM APIs in plain JavaScript, allowing simulation of a browser environment with high fidelity.
Basic happy-dom test example
Example test using happy-dom with Bun: 'import { test, expect } from "bun:test"; test("dom test", () => { document.body.innerHTML = `<button>My button</button>`; const button = document.querySelector("button"); expect(button?.innerText).toEqual("My button"); });'
Install and preload happy-dom
To use happy-dom with Bun tests, first install @happy-dom/global-registrator as a dev dependency with 'bun add -d @happy-dom/global-registrator'. Then create a file called happydom.ts with 'import { GlobalRegistrator } from "@happy-dom/global-registrator"; GlobalRegistrator.register();' and add '[test] preload = ["./happydom.ts"]' to bunfig.toml. This makes browser APIs like document and window available in the global scope before tests run.
Test DOM events and user interactions
DOM events can be tested by adding event listeners to DOM elements using addEventListener, triggering events with .click() or similar methods, and asserting on state changes or side effects using expect().
React Testing Library test example
Example React component test with Bun and React Testing Library: 'import { test, expect } from "bun:test"; import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; function Button({ children }) { return <button>{children}</button>; } test("renders button", () => { render(<Button>Click me</Button>); expect(screen.getByRole("button")).toHaveTextContent("Click me"); });'
Test custom elements and web components
Custom elements can be tested with Bun by defining a class extending HTMLElement, registering it with customElements.define(name, class), then testing it in the DOM using document.querySelector and standard assertions.
React Testing Library with Bun
Bun works with React Testing Library for testing React components. After setting up happy-dom, install @testing-library/react and @testing-library/jest-dom as dev dependencies with 'bun add -d @testing-library/react @testing-library/jest-dom', then use React Testing Library normally in test files.
Performance tips for large DOM test suites
For large test suites with happy-dom, use beforeEach to reset DOM state between tests, avoid creating too many DOM elements in a single test, and use cleanup functions from testing libraries like @testing-library/react's cleanup() function, potentially calling document.body.innerHTML = '' in afterEach hooks.
Global setup with happy-dom and mocking
For more involved DOM testing setups, create a preload file that imports GlobalRegistrator and calls register(), then adds global mocks like ResizeObserver or window.matchMedia using Object.defineProperty or direct assignment. Reference this preload file in bunfig.toml under [test] preload.
AI Agent integration with quiet output
When using Bun's test runner with an AI coding assistant, set environment variables to enable AI-friendly output: CLAUDECODE=1 for Claude Code, REPL_ID=1 for Replit, or AGENT=1 for generic AI agents. When detected, only test failures are displayed in detail, passing/skipped/todo test indicators are hidden, and summary statistics remain intact.
Serial test example with shared state
Example of serial tests with shared state:
import { test, expect } from "bun:test";
let sharedState = 0;
// These tests must run in order
test.serial("first serial test", () => {
sharedState = 1;
expect(sharedState).toBe(1);
});
test.serial("second serial test", () => {
// Depends on the previous test
expect(sharedState).toBe(1);
sharedState = 2;
});
// This test can run concurrently if --concurrent is enabled
test("independent test", () => {
expect(true).toBe(true);
});
// Chaining test qualifiers
test.failing.each([1, 2, 3])("chained qualifiers %d", input => {
expect(input).toBe(0); // This test is expected to fail for each input
});
Test file naming patterns
The test runner recursively searches the working directory for files matching: *.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, *_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, *.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}, or *_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}.
Bun test runner features overview
Bun ships with a fast, built-in, Jest-compatible test runner. Tests run in the Bun runtime and support TypeScript and JSX, lifecycle hooks, snapshot testing, UI & DOM testing, watch mode with --watch, and script pre-loading with --preload.
Test timeout configuration
Use the --timeout flag to specify a per-test timeout in milliseconds. If a test times out, it is marked as failed. The default value is 5000 milliseconds.
Snapshot testing example
Example of snapshot testing:
import { test, expect } from "bun:test";
test("snapshot", () => {
expect({ a: 1 }).toMatchSnapshot();
});
Default test execution model
By default the test runner runs all tests in a single process: it loads all --preload scripts, then runs every file in one shared global. Pass --parallel to spread files across CPU cores instead.
Mock function example
Example of creating and testing a mock function:
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());
test("random", () => {
const val = random();
expect(val).toBeGreaterThan(0);
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(1);
});
Create mock functions
Create mock functions with the mock() function imported from bun:test. Alternatively, use jest.fn() which behaves identically. Example: const random = mock(() => Math.random());
Lifecycle hooks in Bun tests
Bun supports four lifecycle hooks: beforeAll (runs once before all tests), beforeEach (runs before each test), afterEach (runs after each test), and afterAll (runs once after all tests). Define hooks inside test files, or in a separate file preloaded with the --preload flag.
Filter test files and test names
Pass positional arguments to bun test to filter test files by path (glob patterns not yet supported). Use the -t/--test-name-pattern flag to filter by test name. To run a specific file, ensure the path starts with ./ or / to distinguish it from a filter name.
Jest compatibility tracking
Bun aims for compatibility with Jest, but not everything is implemented. Compatibility is tracked in GitHub issue #1825.
Watch mode for tests
bun test accepts the --watch flag to watch for changes and re-run tests, similar to bun run.
Bail out of tests with --bail
Use the --bail flag to abort the test run after a given number of test failures. --bail with no value bails after 1 failure. --bail=10 bails after 10 failures. By default, Bun runs all tests and reports all failures.
Reproduce random test order with --seed
Use the --seed flag to specify the randomization seed and reproduce the same test order when debugging order-dependent failures. The --seed flag implies --randomize, so both flags do not need to be specified together. The same seed always produces the same test execution order.
Randomize test execution order
Use the --randomize flag to run tests in a random order. This helps detect tests that depend on shared state or execution order. The seed used for randomization is displayed in the test summary.
Rerun tests with --rerun-each
Use the --rerun-each flag to run each test multiple times. This surfaces flaky or non-deterministic test failures.
Snapshot testing
bun test supports snapshot testing. Use toMatchSnapshot() to match values against saved snapshots. To update snapshots, use the --update-snapshots flag when running bun test.
Retry failed tests with --retry
Use the --retry flag to automatically retry failed tests up to a given number of times. If a test fails and then passes on a subsequent attempt, it is reported as passing. Per-test { retry: N } option overrides the global --retry value. Can also be set in bunfig.toml under [test] section.
UI and DOM testing support
Bun is compatible with popular UI testing libraries: HappyDOM, DOM Testing Library, and React Testing Library.
Basic test example
Example of a basic test:
import { expect, test } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
Concurrent test example
Example of concurrent and serial tests:
import { test, expect } from "bun:test";
// These tests run in parallel with each other
test.concurrent("concurrent test 1", async () => {
await fetch("/api/endpoint1");
expect(true).toBe(true);
});
test.concurrent("concurrent test 2", async () => {
await fetch("/api/endpoint2");
expect(true).toBe(true);
});
// This test runs sequentially
test("sequential test", () => {
expect(1 + 1).toBe(2);
});
test.serial forces sequential execution
Use test.serial() to force tests to run sequentially, even when the --concurrent flag is enabled. Test qualifiers can be chained, such as test.failing.each().
test.concurrent marks individual tests for parallel execution
Use test.concurrent() to mark individual tests to run concurrently, even when the --concurrent flag is not used. These tests run in parallel with each other but independently of tests marked test.serial or plain test().
Max concurrency limit
Use the --max-concurrency flag to control the maximum number of tests running simultaneously. This helps prevent resource exhaustion when running many concurrent tests. The default value is 20.
Parallel test execution strategy for large codebases
For large test suites, use --parallel to spread files across CPU cores (one worker per core). Then optionally use --no-isolate if files don't leak state (safe default is fresh global per file). Use --shard=i/n to split across CI machines. Use --timings to balance shards by time rather than count, keeping path-neighbours together. Use --update-timings to automatically refresh timings each run.
Concurrent test execution with --concurrent flag
Use the --concurrent flag to run all tests concurrently within their respective files. When enabled, all tests run in parallel unless marked with test.serial.
--parallel flag for test file parallelism
The --parallel flag runs test files across N worker processes (default: number of CPU cores). It implies --isolate unless --no-isolate is specified. Usage: 'bun test --parallel' for one worker per CPU core, or 'bun test --parallel=4' for exactly 4 workers.