> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Lifecycle

> Use beforeAll, beforeEach, afterEach, afterAll, and onTestFinished hooks to set up and tear down state around your tests.

Bun's test runner supports lifecycle hooks for loading fixtures, managing shared resources, and configuring the test environment.

| Hook             | Description                                                 |
| ---------------- | ----------------------------------------------------------- |
| `beforeAll`      | Runs once before all tests in the current scope.            |
| `beforeEach`     | Runs before each test in the current scope.                 |
| `afterEach`      | Runs after each test in the current scope.                  |
| `afterAll`       | Runs once after all tests in the current scope.             |
| `onTestFinished` | Runs after a single test completes (after all `afterEach`). |

## Per-test setup and teardown

Use `beforeEach` and `afterEach` for setup and cleanup that should happen around every test:

```typescript theme={null}
import { beforeEach, afterEach, test, expect } from "bun:test";

let testUser: User;

beforeEach(() => {
  testUser = createTestUser();
});

afterEach(() => {
  cleanupTestUser(testUser);
});

test("updates user profile", () => {
  testUser.name = "Updated";
  expect(testUser.name).toBe("Updated");
});
```

## Per-scope setup and teardown

Use `beforeAll` and `afterAll` for setup and cleanup that should happen once for a group of tests. The scope is determined by where the hook is defined.

### Scoped to a describe block

```typescript theme={null}
import { describe, beforeAll, afterAll, test } from "bun:test";

describe("UserService", () => {
  let db: Database;

  beforeAll(async () => {
    db = await connectTestDatabase();
  });

  afterAll(async () => {
    await db.close();
  });

  test("creates a user", async () => {
    const user = await db.createUser({ name: "Alice" });
    expect(user.id).toBeDefined();
  });

  test("finds a user", async () => {
    const user = await db.findUser("Alice");
    expect(user).not.toBeNull();
  });
});
```

### Scoped to a test file

Hooks defined at the top level of a file apply to all tests in that file:

```typescript theme={null}
import { beforeAll, afterAll, test } from "bun:test";

let server: Server;

beforeAll(async () => {
  server = await startTestServer({ port: 0 });
});

afterAll(async () => {
  await server.stop();
});

test("GET /health returns 200", async () => {
  const res = await fetch(`http://localhost:${server.port}/health`);
  expect(res.status).toBe(200);
});
```

## Global setup and teardown

To run hooks before any test file executes, define them in a preload file:

```typescript setup.ts theme={null}
import { beforeAll, afterAll } from "bun:test";

beforeAll(async () => {
  await startTestInfrastructure();
});

afterAll(async () => {
  await stopTestInfrastructure();
});
```

Register the preload file in `bunfig.toml`:

```toml bunfig.toml theme={null}
[test]
preload = ["./setup.ts"]
```

Or pass it on the command line:

```bash theme={null}
bun test --preload ./setup.ts
```

## Async hooks

All hooks support async functions. Bun awaits each hook before proceeding.

```typescript theme={null}
import { beforeAll, afterAll, test, expect } from "bun:test";

beforeAll(async () => {
  await seedDatabase();
});

afterAll(async () => {
  await clearDatabase();
});

test("reads seeded data", async () => {
  const rows = await db.query("SELECT * FROM users");
  expect(rows.length).toBeGreaterThan(0);
});
```

## onTestFinished

Use `onTestFinished` to register a callback that runs after a specific test completes, after all `afterEach` hooks:

```typescript theme={null}
import { test, onTestFinished } from "bun:test";

test("with per-test cleanup", () => {
  const tempFile = createTempFile();

  onTestFinished(() => {
    deleteTempFile(tempFile);
  });

  // ... test body
});
```

<Note>
  `onTestFinished` is not supported inside concurrent tests. Use `test.serial` for tests that need per-test cleanup via this hook.
</Note>

## Execution order

When hooks are nested inside `describe` blocks, they run in this order:

```typescript theme={null}
import { describe, beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test";

beforeAll(() => console.log("File beforeAll"));
afterAll(() => console.log("File afterAll"));

describe("outer", () => {
  beforeAll(() => console.log("Outer beforeAll"));
  beforeEach(() => console.log("Outer beforeEach"));
  afterEach(() => console.log("Outer afterEach"));
  afterAll(() => console.log("Outer afterAll"));

  describe("inner", () => {
    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");
    });
  });
});
```

Output:

```
File beforeAll
Outer beforeAll
Inner beforeAll
Outer beforeEach
Inner beforeEach
Test running
Inner afterEach
Outer afterEach
Inner afterAll
Outer afterAll
File afterAll
```

## Parameterized tests

### test.each

Run the same test function with multiple inputs:

```typescript theme={null}
import { test, expect } from "bun:test";

test.each([
  [1, 1, 2],
  [2, 3, 5],
  [10, 20, 30],
])("adds %i + %i = %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});
```

Pass objects to use named fields in the title:

```typescript theme={null}
test.each([
  { input: "hello", expected: 5 },
  { input: "world!", expected: 6 },
])("$input has length $expected", ({ input, expected }) => {
  expect(input.length).toBe(expected);
});
```

### describe.each

Create a parameterized suite — every test inside runs once per data set:

```typescript theme={null}
import { describe, test, expect } from "bun:test";

describe.each([
  { role: "admin", canDelete: true },
  { role: "viewer", canDelete: false },
])("$role user", ({ role, canDelete }) => {
  test("can read resources", () => {
    expect(canRead(role)).toBe(true);
  });

  test(`can${canDelete ? "" : "not"} delete resources`, () => {
    expect(canDeleteResource(role)).toBe(canDelete);
  });
});
```

## Error handling

If a hook throws, all tests in its scope are skipped:

```typescript theme={null}
beforeAll(async () => {
  try {
    await connectDatabase();
  } catch (error) {
    console.error("Database setup failed:", error);
    throw error; // causes all tests in scope to be skipped
  }
});
```

## Best practices

<AccordionGroup>
  <Accordion title="Match the hook scope to the resource lifetime">
    Use `beforeAll`/`afterAll` for expensive shared resources (servers, database connections) and `beforeEach`/`afterEach` for per-test state (mock data, DOM resets).

    ```typescript theme={null}
    // Expensive: start once
    beforeAll(async () => { server = await startServer(); });
    afterAll(async () => { await server.stop(); });

    // Cheap: reset per test
    beforeEach(() => { db.seed(testData); });
    afterEach(() => { db.clear(); });
    ```
  </Accordion>

  <Accordion title="Always clean up resources">
    Failing to clean up can cause test pollution and flakiness:

    ```typescript theme={null}
    afterEach(() => {
      document.body.innerHTML = "";
      localStorage.clear();
      jest.resetAllMocks();
    });

    afterAll(async () => {
      await closeDatabase();
      await stopServer();
    });
    ```
  </Accordion>

  <Accordion title="Keep hooks simple">
    Move complex setup logic into helper functions to keep hooks readable and testable:

    ```typescript theme={null}
    beforeEach(async () => {
      await seedTestDatabase();  // encapsulates complexity
    });
    ```
  </Accordion>
</AccordionGroup>
