mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
349 lines
7.9 KiB
Plaintext
349 lines
7.9 KiB
Plaintext
---
|
|
title: "Lifecycle hooks"
|
|
description: "Learn how to use beforeAll, beforeEach, afterEach, and afterAll lifecycle hooks in Bun tests"
|
|
---
|
|
|
|
The test runner supports the following lifecycle hooks. This is useful for loading test fixtures, mocking data, and configuring the test environment.
|
|
|
|
| Hook | Description |
|
|
| ------------ | --------------------------- |
|
|
| `beforeAll` | Runs once before all tests. |
|
|
| `beforeEach` | Runs before each test. |
|
|
| `afterEach` | Runs after each test. |
|
|
| `afterAll` | Runs once after all tests. |
|
|
|
|
## Per-Test Setup and Teardown
|
|
|
|
Perform per-test setup and teardown logic with `beforeEach` and `afterEach`.
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { beforeEach, afterEach, test } from "bun:test";
|
|
|
|
beforeEach(() => {
|
|
console.log("running test.");
|
|
});
|
|
|
|
afterEach(() => {
|
|
console.log("done with test.");
|
|
});
|
|
|
|
// tests...
|
|
test("example test", () => {
|
|
// This test will have beforeEach run before it
|
|
// and afterEach run after it
|
|
});
|
|
```
|
|
|
|
## Per-Scope Setup and Teardown
|
|
|
|
Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. The scope is determined by where the hook is defined.
|
|
|
|
### Scoped to a Describe Block
|
|
|
|
To scope the hooks to a particular describe block:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { describe, beforeAll, afterAll, test } from "bun:test";
|
|
|
|
describe("test group", () => {
|
|
beforeAll(() => {
|
|
// setup for this describe block
|
|
console.log("Setting up test group");
|
|
});
|
|
|
|
afterAll(() => {
|
|
// teardown for this describe block
|
|
console.log("Tearing down test group");
|
|
});
|
|
|
|
test("test 1", () => {
|
|
// test implementation
|
|
});
|
|
|
|
test("test 2", () => {
|
|
// test implementation
|
|
});
|
|
});
|
|
```
|
|
|
|
### Scoped to a Test File
|
|
|
|
To scope the hooks to an entire test file:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { describe, beforeAll, afterAll, test } from "bun:test";
|
|
|
|
beforeAll(() => {
|
|
// setup for entire file
|
|
console.log("Setting up test file");
|
|
});
|
|
|
|
afterAll(() => {
|
|
// teardown for entire file
|
|
console.log("Tearing down test file");
|
|
});
|
|
|
|
describe("test group", () => {
|
|
test("test 1", () => {
|
|
// test implementation
|
|
});
|
|
});
|
|
```
|
|
|
|
## Global Setup and Teardown
|
|
|
|
To scope the hooks to an entire multi-file test run, define the hooks in a separate file.
|
|
|
|
```ts title="setup.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, afterAll } from "bun:test";
|
|
|
|
beforeAll(() => {
|
|
// global setup
|
|
console.log("Global test setup");
|
|
// Initialize database connections, start servers, etc.
|
|
});
|
|
|
|
afterAll(() => {
|
|
// global teardown
|
|
console.log("Global test teardown");
|
|
// Close database connections, stop servers, etc.
|
|
});
|
|
```
|
|
|
|
Then use `--preload` to run the setup script before any test files.
|
|
|
|
```bash terminal icon="terminal"
|
|
bun test --preload ./setup.ts
|
|
```
|
|
|
|
To avoid typing `--preload` every time you run tests, it can be added to your `bunfig.toml`:
|
|
|
|
```toml title="bunfig.toml" icon="settings"
|
|
[test]
|
|
preload = ["./setup.ts"]
|
|
```
|
|
|
|
## Practical Examples
|
|
|
|
### Database Setup
|
|
|
|
```ts title="database-setup.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
|
|
import { createConnection, closeConnection, clearDatabase } from "./db";
|
|
|
|
let connection;
|
|
|
|
beforeAll(async () => {
|
|
// Connect to test database
|
|
connection = await createConnection({
|
|
host: "localhost",
|
|
database: "test_db",
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Close database connection
|
|
await closeConnection(connection);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// Start with clean database for each test
|
|
await clearDatabase(connection);
|
|
});
|
|
```
|
|
|
|
### API Server Setup
|
|
|
|
```ts title="server-setup.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, afterAll } from "bun:test";
|
|
import { startServer, stopServer } from "./server";
|
|
|
|
let server;
|
|
|
|
beforeAll(async () => {
|
|
// Start test server
|
|
server = await startServer({
|
|
port: 3001,
|
|
env: "test",
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Stop test server
|
|
await stopServer(server);
|
|
});
|
|
```
|
|
|
|
### Mock Setup
|
|
|
|
```ts title="mock-setup.ts" icon="/icons/typescript.svg"
|
|
import { beforeEach, afterEach } from "bun:test";
|
|
import { mock } from "bun:test";
|
|
|
|
beforeEach(() => {
|
|
// Set up common mocks
|
|
mock.module("./api-client", () => ({
|
|
fetchUser: mock(() => Promise.resolve({ id: 1, name: "Test User" })),
|
|
createUser: mock(() => Promise.resolve({ id: 2 })),
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clear all mocks after each test
|
|
mock.restore();
|
|
});
|
|
```
|
|
|
|
## Async Lifecycle Hooks
|
|
|
|
All lifecycle hooks support async functions:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, afterAll, test } from "bun:test";
|
|
|
|
beforeAll(async () => {
|
|
// Async setup
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
console.log("Async setup complete");
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Async teardown
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
console.log("Async teardown complete");
|
|
});
|
|
|
|
test("async test", async () => {
|
|
// Test will wait for beforeAll to complete
|
|
await expect(Promise.resolve("test")).resolves.toBe("test");
|
|
});
|
|
```
|
|
|
|
## Nested Hooks
|
|
|
|
Hooks can be nested and will run in the appropriate order:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
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");
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
```txt
|
|
// Output order:
|
|
// File beforeAll
|
|
// Outer beforeAll
|
|
// Inner beforeAll
|
|
// Outer beforeEach
|
|
// Inner beforeEach
|
|
// Test running
|
|
// Inner afterEach
|
|
// Outer afterEach
|
|
// Inner afterAll
|
|
// Outer afterAll
|
|
// File afterAll
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
If a lifecycle hook throws an error, it will affect test execution:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, test } from "bun:test";
|
|
|
|
beforeAll(() => {
|
|
// If this throws, all tests in this scope will be skipped
|
|
throw new Error("Setup failed");
|
|
});
|
|
|
|
test("this test will be skipped", () => {
|
|
// This won't run because beforeAll failed
|
|
});
|
|
```
|
|
|
|
For better error handling:
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { beforeAll, test, expect } from "bun:test";
|
|
|
|
beforeAll(async () => {
|
|
try {
|
|
await setupDatabase();
|
|
} catch (error) {
|
|
console.error("Database setup failed:", error);
|
|
throw error; // Re-throw to fail the test suite
|
|
}
|
|
});
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Keep Hooks Simple
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
// Good: Simple, focused setup
|
|
beforeEach(() => {
|
|
clearLocalStorage();
|
|
resetMocks();
|
|
});
|
|
|
|
// Avoid: Complex logic in hooks
|
|
beforeEach(async () => {
|
|
// Too much complex logic makes tests hard to debug
|
|
const data = await fetchComplexData();
|
|
await processData(data);
|
|
await setupMultipleServices(data);
|
|
});
|
|
```
|
|
|
|
### Use Appropriate Scope
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
// Good: File-level setup for shared resources
|
|
beforeAll(async () => {
|
|
await startTestServer();
|
|
});
|
|
|
|
// Good: Test-level setup for test-specific state
|
|
beforeEach(() => {
|
|
user = createTestUser();
|
|
});
|
|
```
|
|
|
|
### Clean Up Resources
|
|
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
|
import { afterAll, afterEach } from "bun:test";
|
|
|
|
afterEach(() => {
|
|
// Clean up after each test
|
|
document.body.innerHTML = "";
|
|
localStorage.clear();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Clean up expensive resources
|
|
await closeDatabase();
|
|
await stopServer();
|
|
});
|
|
```
|