Bun Test Runner
Bun has a built-in fast test runner that is compatible with Jest syntax, supports TypeScript, and requires no additional configuration. This chapter introduces the usage of Bun testing.
Quick Start
Creating a Test File
typescript
// math.test.ts
import { expect, test } from "bun:test";
function add(a: number, b: number): number {
return a + b;
}
test("addition test", () => {
expect(add(2, 3)).toBe(5);
});Running Tests
bash
# Run all tests
bun test
# Run specific file
bun test math.test.ts
# Run matching test files
bun test --pattern "*.spec.ts"
# Watch mode
bun test --watchTest Syntax
test Function
typescript
import { test, expect } from "bun:test";
// Basic test
test("basic test", () => {
expect(1 + 1).toBe(2);
});
// Async test
test("async test", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
// Skip test
test.skip("skipped test", () => {
// Will not execute
});
// Only run this test
test.only("only run this", () => {
expect(true).toBe(true);
});
// Todo test
test.todo("feature to be implemented");
// Test with timeout
test("timeout test", async () => {
await Bun.sleep(100);
}, { timeout: 5000 });describe Grouping
typescript
import { describe, test, expect } from "bun:test";
describe("math operations", () => {
test("addition", () => {
expect(1 + 1).toBe(2);
});
test("subtraction", () => {
expect(5 - 3).toBe(2);
});
describe("advanced operations", () => {
test("multiplication", () => {
expect(2 * 3).toBe(6);
});
test("division", () => {
expect(6 / 2).toBe(3);
});
});
});Lifecycle Hooks
typescript
import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
describe("lifecycle test", () => {
let db: Database;
beforeAll(async () => {
// Execute once before all tests
db = await Database.connect();
console.log("Database connected");
});
afterAll(async () => {
// Execute once after all tests
await db.close();
console.log("Database closed");
});
beforeEach(() => {
// Execute before each test
console.log("Preparing test data");
});
afterEach(() => {
// Execute after each test
console.log("Cleaning up test data");
});
test("test 1", () => {
expect(db.isConnected()).toBe(true);
});
test("test 2", () => {
expect(db.isConnected()).toBe(true);
});
});Assertion Methods
Basic Assertions
typescript
import { expect, test } from "bun:test";
test("basic assertions", () => {
// Equality
expect(1 + 1).toBe(2);
expect({ a: 1 }).toEqual({ a: 1 });
// Inequality
expect(1).not.toBe(2);
// Truthy/falsy
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect("hello").toBeDefined();
// Type checking
expect(typeof "hello").toBe("string");
expect([]).toBeInstanceOf(Array);
});Number Assertions
typescript
test("number assertions", () => {
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(5).toBeLessThanOrEqual(5);
// Floating point comparison
expect(0.1 + 0.2).toBeCloseTo(0.3);
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // 5 digits precision
// NaN check
expect(NaN).toBeNaN();
});String Assertions
typescript
test("string assertions", () => {
expect("hello world").toContain("world");
expect("hello world").toMatch(/hello/);
expect("hello world").toStartWith("hello");
expect("hello world").toEndWith("world");
expect("hello").toHaveLength(5);
});Array Assertions
typescript
test("array assertions", () => {
const arr = [1, 2, 3];
expect(arr).toContain(2);
expect(arr).toHaveLength(3);
expect(arr).toEqual([1, 2, 3]);
// Array containing objects
const users = [{ id: 1, name: "张三" }];
expect(users).toContainEqual({ id: 1, name: "张三" });
});Object Assertions
typescript
test("object assertions", () => {
const obj = { a: 1, b: 2, c: { d: 3 } };
expect(obj).toEqual({ a: 1, b: 2, c: { d: 3 } });
expect(obj).toMatchObject({ a: 1, b: 2 });
expect(obj).toHaveProperty("a");
expect(obj).toHaveProperty("a", 1);
expect(obj).toHaveProperty("c.d", 3);
});Exception Assertions
typescript
test("exception assertions", () => {
function throwError() {
throw new Error("Error occurred");
}
expect(throwError).toThrow();
expect(throwError).toThrow("Error occurred");
expect(throwError).toThrow(/Error/);
expect(throwError).toThrow(Error);
});
test("async exception", async () => {
async function asyncThrow() {
throw new Error("Async error");
}
expect(asyncThrow()).rejects.toThrow("Async error");
});Mock Functions
Mock Functions
typescript
import { test, expect, mock } from "bun:test";
test("mock function", () => {
// Create mock function
const fn = mock(() => 42);
// Call
expect(fn()).toBe(42);
expect(fn(1, 2)).toBe(42);
// Verify calls
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith(1, 2);
});
test("mock implementation", () => {
const fn = mock();
// Set return value
fn.mockReturnValue(100);
expect(fn()).toBe(100);
// Set one-time return value
fn.mockReturnValueOnce(1);
fn.mockReturnValueOnce(2);
expect(fn()).toBe(1);
expect(fn()).toBe(2);
expect(fn()).toBe(100);
// Set implementation
fn.mockImplementation((a, b) => a + b);
expect(fn(2, 3)).toBe(5);
});Spy Functions
typescript
import { test, expect, spyOn } from "bun:test";
test("spy function", () => {
const obj = {
greet(name: string) {
return `Hello, ${name}!`;
},
};
// Spy on method
const spy = spyOn(obj, "greet");
// Call original method
expect(obj.greet("Bun")).toBe("Hello, Bun!");
// Verify calls
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith("Bun");
// Restore original method
spy.mockRestore();
});Mock Modules
typescript
import { test, expect, mock } from "bun:test";
// Mock entire module
mock.module("./api", () => ({
fetchUsers: mock(() => Promise.resolve([{ id: 1, name: "张三" }])),
fetchPosts: mock(() => Promise.resolve([])),
}));
// Now importing mock version
import { fetchUsers, fetchPosts } from "./api";
test("mock module", async () => {
const users = await fetchUsers();
expect(users).toHaveLength(1);
expect(users[0].name).toBe("张三");
});Snapshot Testing
Basic Snapshots
typescript
import { test, expect } from "bun:test";
test("snapshot test", () => {
const user = {
id: 1,
name: "张三",
email: "zhangsan@example.com",
createdAt: new Date("2024-01-01"),
};
expect(user).toMatchSnapshot();
});Updating Snapshots
bash
# Update all snapshots
bun test --update-snapshots
# Short form
bun test -uInline Snapshots
typescript
test("inline snapshot", () => {
const result = { a: 1, b: 2 };
expect(result).toMatchInlineSnapshot(`
{
"a": 1,
"b": 2,
}
`);
});Code Coverage
Enable Coverage
bash
# Run tests and generate coverage report
bun test --coverageCoverage Output
----------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------|---------|----------|---------|---------|
All files | 85.71 | 75.00 | 66.67 | 85.71 |
math.ts | 100.00 | 100.00 | 100.00 | 100.00 |
utils.ts | 75.00 | 50.00 | 50.00 | 75.00 |
----------|---------|----------|---------|---------|Coverage Configuration
toml
# bunfig.toml
[test]
coverage = true
coverageThreshold = 80
# Ignored files
coverageIgnorePatterns = [
"node_modules",
"test",
"*.config.*",
]Test Configuration
bunfig.toml Configuration
toml
[test]
# Test file matching
testMatch = ["**/*.test.ts", "**/*.spec.ts"]
# Timeout (milliseconds)
timeout = 5000
# Preload script
preload = ["./test/setup.ts"]
# Enable coverage
coverage = true
# Coverage threshold
coverageThreshold = 80Test Setup File
typescript
// test/setup.ts
import { beforeAll, afterAll } from "bun:test";
// Global setup
beforeAll(() => {
console.log("Starting tests...");
});
afterAll(() => {
console.log("Tests complete!");
});
// Global mock
global.fetch = mock(() =>
Promise.resolve(new Response("mocked"))
);Async Testing
Promise Testing
typescript
test("promise test", async () => {
const promise = Promise.resolve(42);
await expect(promise).resolves.toBe(42);
const rejected = Promise.reject(new Error("Failed"));
await expect(rejected).rejects.toThrow("Failed");
});Timer Testing
typescript
import { test, expect, setSystemTime } from "bun:test";
test("timer test", () => {
// Mock system time
setSystemTime(new Date("2024-01-01T00:00:00Z"));
expect(new Date().toISOString()).toBe("2024-01-01T00:00:00.000Z");
// Restore real time
setSystemTime();
});Practical Test Examples
API Service Testing
typescript
// api.test.ts
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
describe("API testing", () => {
let server: ReturnType<typeof Bun.serve>;
let baseUrl: string;
beforeAll(() => {
server = Bun.serve({
port: 0, // Random port
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/api/users") {
return Response.json([{ id: 1, name: "张三" }]);
}
return new Response("Not Found", { status: 404 });
},
});
baseUrl = `http://localhost:${server.port}`;
});
afterAll(() => {
server.stop();
});
test("get user list", async () => {
const response = await fetch(`${baseUrl}/api/users`);
expect(response.ok).toBe(true);
const users = await response.json();
expect(users).toHaveLength(1);
expect(users[0].name).toBe("张三");
});
test("404 response", async () => {
const response = await fetch(`${baseUrl}/not-found`);
expect(response.status).toBe(404);
});
});Database Testing
typescript
// db.test.ts
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { Database } from "bun:sqlite";
describe("database testing", () => {
let db: Database;
beforeEach(() => {
db = new Database(":memory:");
db.run(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)
`);
});
afterEach(() => {
db.close();
});
test("insert user", () => {
const stmt = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
stmt.run("张三", "zhangsan@example.com");
const user = db.query("SELECT * FROM users WHERE name = ?").get("张三");
expect(user).toMatchObject({ name: "张三", email: "zhangsan@example.com" });
});
test("query users", () => {
db.run("INSERT INTO users (name, email) VALUES ('张三', 'a@b.com')");
db.run("INSERT INTO users (name, email) VALUES ('李四', 'c@d.com')");
const users = db.query("SELECT * FROM users").all();
expect(users).toHaveLength(2);
});
});Summary
This chapter covered:
- ✅ Test file creation and execution
- ✅ test, describe, and lifecycle hooks
- ✅ Various assertion methods
- ✅ Mock and Spy functions
- ✅ Snapshot testing
- ✅ Code coverage
- ✅ Practical test examples
Next Steps
Continue reading Hot Reload to learn about Bun's development-time auto-reload feature.