Bun’s test runner provides lifecycle hooks to set up and tear down test state. These hooks are compatible with Jest.
Available hooks
beforeAll
Runs once before all tests in a describe block:
import { describe, test, beforeAll, expect } from "bun:test";
describe("database tests", () => {
let db;
beforeAll(async () => {
// Runs once before all tests
db = await connectToDatabase();
});
test("query 1", () => {
expect(db.query("SELECT 1")).toBe(1);
});
test("query 2", () => {
expect(db.query("SELECT 2")).toBe(2);
});
});
beforeEach
Runs before each test:
import { describe, test, beforeEach, expect } from "bun:test";
describe("counter tests", () => {
let counter;
beforeEach(() => {
// Runs before each test
counter = 0;
});
test("increment", () => {
counter++;
expect(counter).toBe(1);
});
test("increment twice", () => {
counter++;
counter++;
expect(counter).toBe(2);
});
});
afterEach
Runs after each test:
import { describe, test, afterEach } from "bun:test";
describe("cleanup tests", () => {
afterEach(() => {
// Clean up after each test
cleanupTempFiles();
});
test("creates temp file", () => {
createTempFile("test1.txt");
});
test("creates another temp file", () => {
createTempFile("test2.txt");
});
});
afterAll
Runs once after all tests in a describe block:
import { describe, test, beforeAll, afterAll } from "bun:test";
describe("server tests", () => {
let server;
beforeAll(async () => {
server = await startServer();
});
afterAll(async () => {
// Runs once after all tests
await server.stop();
});
test("responds to requests", async () => {
const response = await fetch(server.url);
expect(response.status).toBe(200);
});
});
Async hooks
All hooks support async operations:
import { beforeEach, afterEach, test } from "bun:test";
beforeEach(async () => {
await database.connect();
await database.seed();
});
afterEach(async () => {
await database.clear();
await database.disconnect();
});
test("database query", async () => {
const result = await database.query("SELECT * FROM users");
expect(result).toHaveLength(5);
});
Hook execution order
Hooks execute in the following order:
beforeAll (outer describe)
beforeAll (inner describe)
beforeEach (outer describe)
beforeEach (inner describe)
- test
afterEach (inner describe)
afterEach (outer describe)
afterAll (inner describe)
afterAll (outer describe)
Example:
import { describe, test, beforeAll, beforeEach, afterEach, afterAll } from "bun:test";
describe("outer", () => {
beforeAll(() => console.log("outer beforeAll"));
beforeEach(() => console.log("outer beforeEach"));
afterEach(() => console.log("outer afterEach"));
afterAll(() => console.log("outer afterAll"));
test("outer test", () => {
console.log("outer test");
});
describe("inner", () => {
beforeAll(() => console.log("inner beforeAll"));
beforeEach(() => console.log("inner beforeEach"));
afterEach(() => console.log("inner afterEach"));
afterAll(() => console.log("inner afterAll"));
test("inner test", () => {
console.log("inner test");
});
});
});
// Output:
// outer beforeAll
// outer beforeEach
// outer test
// outer afterEach
// inner beforeAll
// outer beforeEach
// inner beforeEach
// inner test
// inner afterEach
// outer afterEach
// inner afterAll
// outer afterAll
Scoping
Hooks are scoped to the describe block they’re defined in:
import { describe, test, beforeEach } from "bun:test";
beforeEach(() => {
// Runs before all tests in file
console.log("global beforeEach");
});
describe("suite 1", () => {
beforeEach(() => {
// Only runs before tests in suite 1
console.log("suite 1 beforeEach");
});
test("test 1", () => {});
});
describe("suite 2", () => {
beforeEach(() => {
// Only runs before tests in suite 2
console.log("suite 2 beforeEach");
});
test("test 2", () => {});
});
Timeout
Hooks can have custom timeouts:
import { beforeAll } from "bun:test";
beforeAll(async () => {
// Custom timeout of 30 seconds
await slowSetup();
}, 30000);
Done callback
Hooks support the done callback pattern:
import { beforeEach } from "bun:test";
beforeEach((done) => {
setTimeout(() => {
// Setup complete
done();
}, 100);
});
Prefer using async/await over done callbacks when possible.
Error handling
If a hook throws an error:
beforeAll/beforeEach: The test is marked as failed
afterEach/afterAll: The test result is preserved, but the error is logged
import { beforeEach, test, expect } from "bun:test";
beforeEach(() => {
if (!setupSuccessful()) {
throw new Error("Setup failed");
}
});
test("will fail if setup fails", () => {
expect(true).toBe(true);
});
Resource cleanup with using
Bun supports JavaScript’s using keyword for automatic resource cleanup:
import { test } from "bun:test";
test("automatic cleanup", () => {
using server = createTestServer();
// server.stop() called automatically when test ends
const response = fetch(server.url);
expect(response.status).toBe(200);
});