Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zhcndoc/bun/llms.txt
Use this file to discover all available pages before exploring further.
Bun’s test runner has specific runtime behavior that affects how tests are executed, how state is managed, and how resources are handled.
Test isolation
File-level isolation
Each test file runs in its own module scope:
// test1.test.ts
let counter = 0;
test("increment", () => {
counter++;
expect(counter).toBe(1);
});
// test2.test.ts
let counter = 0; // Different counter, separate module
test("increment", () => {
counter++;
expect(counter).toBe(1); // Also 1
});
Tests in different files don’t share global state.
Test-level isolation
Tests within the same file share the module scope:
import { test, expect } from "bun:test";
let counter = 0;
test("first test", () => {
counter++;
expect(counter).toBe(1);
});
test("second test", () => {
counter++; // counter is now 2
expect(counter).toBe(2);
});
Use beforeEach to reset state:
import { test, beforeEach, expect } from "bun:test";
let counter: number;
beforeEach(() => {
counter = 0;
});
test("first test", () => {
counter++;
expect(counter).toBe(1);
});
test("second test", () => {
counter++;
expect(counter).toBe(1); // Reset to 0 by beforeEach
});
Execution model
Sequential execution
By default, tests in a file run sequentially:
test("test 1", () => console.log("1"));
test("test 2", () => console.log("2"));
test("test 3", () => console.log("3"));
// Output: 1, 2, 3
Concurrent execution
Mark tests as concurrent:
import { test } from "bun:test";
test.concurrent("test 1", async () => {
await delay(100);
console.log("1");
});
test.concurrent("test 2", async () => {
await delay(50);
console.log("2");
});
test.concurrent("test 3", async () => {
await delay(10);
console.log("3");
});
// Output: 3, 2, 1 (fastest first)
File-level concurrency
Run multiple test files in parallel:
Or configure in bunfig.toml:
Global state
Process environment
Environment variables are shared across all tests:
import { test, expect } from "bun:test";
process.env.TEST_VAR = "initial";
test("test 1", () => {
process.env.TEST_VAR = "modified";
});
test("test 2", () => {
expect(process.env.TEST_VAR).toBe("modified"); // Sees change from test 1
});
Reset in beforeEach:
import { test, beforeEach } from "bun:test";
const originalEnv = { ...process.env };
beforeEach(() => {
process.env = { ...originalEnv };
});
Global objects
Changes to global objects persist:
import { test, expect } from "bun:test";
(globalThis as any).myGlobal = "initial";
test("test 1", () => {
(globalThis as any).myGlobal = "modified";
});
test("test 2", () => {
expect((globalThis as any).myGlobal).toBe("modified");
});
Module caching
Modules are cached per test file:
// utils.ts
export const timestamp = Date.now();
// test1.test.ts
import { timestamp } from "./utils";
test("test", () => {
console.log(timestamp); // Always the same value
});
To clear cache, use dynamic import:
import { test } from "bun:test";
test("test", async () => {
const { timestamp } = await import("./utils.ts");
console.log(timestamp); // Fresh import
});
Resource management
Automatic cleanup with using
Bun supports JavaScript’s using keyword:
import { test } from "bun:test";
test("resource cleanup", () => {
using server = createTestServer();
// server.stop() called automatically
});
Manual cleanup
Use afterEach or afterAll for cleanup:
import { test, afterEach } from "bun:test";
const servers: Server[] = [];
afterEach(() => {
servers.forEach(s => s.stop());
servers.length = 0;
});
test("test 1", () => {
const server = createServer();
servers.push(server);
});
Error handling
Unhandled promise rejections
Bun catches unhandled rejections and fails the test:
import { test, expect } from "bun:test";
test("unhandled rejection", () => {
Promise.reject(new Error("Oops")); // Test fails
});
Uncaught exceptions
Uncaught exceptions fail the test:
import { test } from "bun:test";
test("uncaught exception", () => {
setTimeout(() => {
throw new Error("Oops"); // Test fails
}, 0);
});
Async test completion
Async tests must complete:
import { test, expect } from "bun:test";
test("async test", async () => {
const result = await asyncOperation();
expect(result).toBe(42);
// Test completes when promise resolves
});
Memory management
Garbage collection
Bun runs garbage collection between test files:
import { test } from "bun:test";
test("creates large array", () => {
const large = new Array(1_000_000).fill(0);
// Memory freed after test file completes
});
Memory leaks
Avoid keeping references in global scope:
// ❌ Bad: Accumulates memory
const results: any[] = [];
test("test 1", () => {
results.push(generateLargeData());
});
test("test 2", () => {
results.push(generateLargeData());
});
// ✅ Good: Cleans up
let results: any[] = [];
afterEach(() => {
results = [];
});
Test startup time
First test in a file includes:
- Module loading
- Dependency resolution
- Global setup
import { test } from "bun:test";
test("first test", () => {
// Includes startup overhead
});
test("second test", () => {
// No startup overhead
});
Optimization tips
-
Minimize global imports:
// ✅ Good: Import only what you need
import { test, expect } from "bun:test";
// ❌ Bad: Imports entire library
import * as everything from "bun:test";
-
Use lazy imports for heavy dependencies:
test("heavy import", async () => {
const { heavy } = await import("./heavy-lib");
});
-
Share expensive setup with beforeAll:
let db;
beforeAll(async () => {
db = await connectToDatabase();
});
Timeout behavior
Default timeout
Tests timeout after 5 seconds by default:
import { test } from "bun:test";
test("slow test", async () => {
await delay(10000); // Fails: timeout
});
Custom timeout
Set per-test timeout:
test("slow test", async () => {
await delay(10000);
}, 15000); // 15 second timeout
Infinite operations
Ensure tests complete:
// ❌ Bad: Never completes
test("infinite loop", () => {
while (true) {}
});
// ✅ Good: Completes
test("finite loop", () => {
let count = 0;
while (count < 100) {
count++;
}
});
Exit behavior
Normal exit
Bun exits with code 0 if all tests pass:
$ bun test
# Exit code: 0
Failure exit
Bun exits with code 1 if any test fails:
$ bun test
# Exit code: 1
Hanging processes
Bun waits for async operations to complete:
import { test } from "bun:test";
test("hanging test", () => {
setInterval(() => {}, 1000); // Prevents exit
});
Clean up resources:
import { test, afterAll } from "bun:test";
const intervals: Timer[] = [];
afterAll(() => {
intervals.forEach(clearInterval);
});
test("clean test", () => {
const interval = setInterval(() => {}, 1000);
intervals.push(interval);
});