new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Bun · Test runner · all subjects

test/lifecycle

28 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Lifecycle hooks overview

Bun test runner supports five lifecycle hooks: beforeAll (runs once before all tests), beforeEach (runs before each test), afterEach (runs after each test), afterAll (runs once after all tests), and onTestFinished (runs after a single test finishes, after all afterEach hooks).

onTestFinished hook not supported in concurrent tests

The onTestFinished hook is not supported in concurrent tests. Use test.serial instead for tests that require onTestFinished.

beforeEach and afterEach per-test setup and teardown

Use beforeEach to perform setup before each individual test runs, and afterEach to perform cleanup after each test completes. These hooks run for every test in their scope.

beforeAll and afterAll hook scope

The scope of beforeAll and afterAll hooks is determined by where they are defined. They can be scoped to a describe block if defined within it, scoped to an entire test file if defined at file level, or scoped to a global multi-file test run if defined in a separate setup file and loaded with --preload.

Global setup and teardown with --preload

To run hooks globally across all test files, define beforeAll and afterAll in a separate setup file, then run tests with --preload flag: bun test --preload ./setup.ts. To avoid typing --preload every time, add preload = ["./setup.ts"] to the [test] section of bunfig.toml.

All lifecycle hooks support async functions

beforeAll, beforeEach, afterEach, afterAll, and onTestFinished all support async functions. Tests wait for async beforeAll hooks to complete before running.

Nested hooks execution order

When using nested describe blocks, hooks execute in a specific order: File beforeAll, then outer describe beforeAll, then inner describe beforeAll, then outer beforeEach, then inner beforeEach, then the test runs, then inner afterEach, then outer afterEach, then inner afterAll, then outer afterAll, then file afterAll.

beforeAll error handling skips tests in scope

If a beforeAll hook throws an error, every test in its scope is skipped. The test suite will fail. To handle setup failures, catch the error, log it, and re-throw it to fail the suite.

Example: Per-test setup and teardown with beforeEach and afterEach

import { beforeEach, afterEach, test } from "bun:test"; beforeEach(() => { console.log("running test."); }); afterEach(() => { console.log("done with test."); }); test("example test", () => { // This test will have beforeEach run before it // and afterEach run after it });

Example: Describe block scoped hooks

import { describe, beforeAll, afterAll, test } from "bun:test"; describe("test group", () => { beforeAll(() => { console.log("Setting up test group"); }); afterAll(() => { console.log("Tearing down test group"); }); test("test 1", () => { // test implementation }); test("test 2", () => { // test implementation }); });

Example: File-level scoped hooks

import { describe, beforeAll, afterAll, test } from "bun:test"; beforeAll(() => { console.log("Setting up test file"); }); afterAll(() => { console.log("Tearing down test file"); }); describe("test group", () => { test("test 1", () => { // test implementation }); });

Example: onTestFinished hook

import { test, onTestFinished } from "bun:test"; test("cleanup after test", () => { onTestFinished(() => { console.log("test finished"); }); });

Example: Global setup and teardown

In setup.ts: import { beforeAll, afterAll } from "bun:test"; beforeAll(() => { console.log("Global test setup"); }); afterAll(() => { console.log("Global test teardown"); }); Then run: bun test --preload ./setup.ts

Example: Database setup with lifecycle hooks

import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test"; import { createConnection, closeConnection, clearDatabase } from "./db"; let connection; beforeAll(async () => { connection = await createConnection({ host: "localhost", database: "test_db", }); }); afterAll(async () => { await closeConnection(connection); }); beforeEach(async () => { await clearDatabase(connection); });

Example: API server setup with lifecycle hooks

import { beforeAll, afterAll } from "bun:test"; import { startServer, stopServer } from "./server"; let server; beforeAll(async () => { server = await startServer({ port: 3001, env: "test", }); }); afterAll(async () => { await stopServer(server); });

Example: Mock setup with lifecycle hooks

import { beforeEach, afterEach } from "bun:test"; import { mock } from "bun:test"; beforeEach(() => { mock.module("./api-client", () => ({ fetchUser: mock(() => Promise.resolve({ id: 1, name: "Test User" })), createUser: mock(() => Promise.resolve({ id: 2 })), })); }); afterEach(() => { mock.restore(); });

Example: Async lifecycle hooks

import { beforeAll, afterAll, test } from "bun:test"; beforeAll(async () => { await new Promise(resolve => setTimeout(resolve, 100)); console.log("Async setup complete"); }); afterAll(async () => { await new Promise(resolve => setTimeout(resolve, 100)); console.log("Async teardown complete"); }); test("async test", async () => { await expect(Promise.resolve("test")).resolves.toBe("test"); });

Example: Nested hooks execution

import { describe, beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test"; beforeAll(() => console.log("File beforeAll")); afterAll(() => console.log("File afterAll")); describe("outer describe", () => { beforeAll(() => console.log("Outer beforeAll")); beforeEach(() => console.log("Outer beforeEach")); afterEach(() => console.log("Outer afterEach")); afterAll(() => console.log("Outer afterAll")); describe("inner describe", () => { beforeAll(() => console.log("Inner beforeAll")); beforeEach(() => console.log("Inner beforeEach")); afterEach(() => console.log("Inner afterEach")); afterAll(() => console.log("Inner afterAll")); test("nested test", () => { console.log("Test running"); }); }); }); // Execution order: File beforeAll, Outer beforeAll, Inner beforeAll, Outer beforeEach, Inner beforeEach, Test running, Inner afterEach, Outer afterEach, Inner afterAll, Outer afterAll, File afterAll

Example: beforeAll error handling

import { beforeAll, test, expect } from "bun:test"; beforeAll(async () => { try { await setupDatabase(); } catch (error) { console.error("Database setup failed:", error); throw error; } });

Best practice: Keep hooks simple and focused

Lifecycle hooks should be simple and focused on specific setup and teardown tasks. Avoid putting complex logic in hooks as it makes tests hard to debug. Good hooks do things like clearLocalStorage() or resetMocks(). Avoid doing complex operations like fetching data, processing it, and setting up multiple services in a single hook.

Best practice: Use appropriate hook scope

Use file-level beforeAll and afterAll for expensive shared resources like test servers. Use test-level beforeEach and afterEach for test-specific state like creating individual test users. This improves test performance and clarity.

Best practice: Clean up resources in afterEach and afterAll

Always clean up resources in afterEach and afterAll hooks. For each test, clear state like document.body.innerHTML and localStorage.clear(). For expensive resources, use afterAll to close database connections and stop servers.

Preload-level hooks with --parallel and --no-isolate

With --parallel --no-isolate, preload-level beforeAll/afterAll hooks still wrap every file, since a worker never knows which file is its last.

Test isolation requires cleanup with afterEach

Since tests run in the same process, ensure proper cleanup in afterEach hooks to clean up global state, delete environment variables, and restore mocked functions to prevent state leakage between tests.

Async tests with async/await

Test functions can be declared as async to test asynchronous code. Await promises directly within the test function.

Async tests with done callback

Alternatively, test functions can accept a 'done' parameter as a callback. The test must call done() to signal completion, or it will hang.

Timeout behavior kills child processes

When a test times out, Bun throws an uncatchable exception to force the test to stop and fail. Bun also kills any child processes spawned in the test (via Bun.spawn, Bun.spawnSync, or node:child_process) to prevent zombie processes.

Setup with beforeEach and teardown with afterEach

Use beforeEach() to run setup code before each test and afterEach() to run cleanup code after each test. These are imported from bun:test.

Give your agent this brain