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

# 生命周期钩子

> 了解如何在 Bun 测试中使用 beforeAll、beforeEach、afterEach 和 afterAll 生命周期钩子

测试运行器支持以下生命周期钩子。这对于加载测试夹具、模拟数据以及配置测试环境非常有用。

| 钩子               | 描述                              |
| ---------------- | ------------------------------- |
| `beforeAll`      | 在所有测试之前运行一次。                    |
| `beforeEach`     | 在每个测试之前运行。                      |
| `afterEach`      | 在每个测试之后运行。                      |
| `afterAll`       | 在所有测试之后运行一次。                    |
| `onTestFinished` | 在单个测试完成后运行（在所有 `afterEach` 之后）。 |

## 每个测试的设置与拆卸

使用 `beforeEach` 和 `afterEach` 执行每个测试的设置与拆卸逻辑。

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeEach, afterEach, test } from "bun:test";

beforeEach(() => {
  console.log("运行测试。");
});

afterEach(() => {
  console.log("测试完成。");
});

// 测试...
test("示例测试", () => {
  // 此测试将在它之前运行 beforeEach
  // 并在它之后运行 afterEach
});
```

## 每个作用域的设置与拆卸

使用 `beforeAll` 和 `afterAll` 执行每个作用域的设置与拆卸逻辑。作用域由钩子定义的位置决定。

### 限定在 describe 块内

要将钩子限定在特定的 describe 块内：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, afterAll, test } from "bun:test";

describe("测试组", () => {
  beforeAll(() => {
    // 此 describe 块的设置
    console.log("设置测试组");
  });

  afterAll(() => {
    // 此 describe 块的拆卸
    console.log("拆卸测试组");
  });

  test("测试 1", () => {
    // 测试实现
  });

  test("测试 2", () => {
    // 测试实现
  });
});
```

### 限定在测试文件内

要将钩子限定在整个测试文件范围：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, afterAll, test } from "bun:test";

beforeAll(() => {
  // 整个文件的设置
  console.log("设置测试文件");
});

afterAll(() => {
  // 整个文件的拆卸
  console.log("拆卸测试文件");
});

describe("测试组", () => {
  test("测试 1", () => {
    // 测试实现
  });
});
```

### `onTestFinished`

使用 `onTestFinished` 来在单个测试完成后运行回调。它会在所有 `afterEach` 钩子之后运行。

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, onTestFinished } from "bun:test";

test("测试后的清理", () => {
  onTestFinished(() => {
    // 在所有 afterEach 钩子之后运行
    console.log("测试完成");
  });
});
```

并发测试不支持该钩子；请改用 `test.serial`。

## 全局设置与拆卸

要将钩子限定于整个多文件测试运行，请在单独文件中定义钩子。

```ts title="setup.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll } from "bun:test";

beforeAll(() => {
  // 全局设置
  console.log("全局测试设置");
  // 初始化数据库连接、启动服务器等
});

afterAll(() => {
  // 全局拆卸
  console.log("全局测试拆卸");
  // 关闭数据库连接、停止服务器等
});
```

然后使用 `--preload` 在任何测试文件之前运行设置脚本。

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --preload ./setup.ts
```

为了避免每次运行测试时都输入 `--preload`，可以将其添加到你的 `bunfig.toml`：

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./setup.ts"]
```

## 实用示例

### 数据库设置

```ts title="database-setup.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
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);
});
```

### API 服务器设置

```ts title="server-setup.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
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);
});
```

### 模拟设置

```ts title="mock-setup.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeEach, afterEach } from "bun:test";
import { mock } from "bun:test";

beforeEach(() => {
  // 设置通用模拟
  mock.module("./api-client", () => ({
    fetchUser: mock(() => Promise.resolve({ id: 1, name: "测试用户" })),
    createUser: mock(() => Promise.resolve({ id: 2 })),
  }));
});

afterEach(() => {
  // 每个测试后清除所有模拟
  mock.restore();
});
```

## 异步生命周期钩子

所有生命周期钩子都支持异步函数：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll, test } from "bun:test";

beforeAll(async () => {
  // 异步设置
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log("异步设置完成");
});

afterAll(async () => {
  // 异步拆卸
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log("异步拆卸完成");
});

test("异步测试", async () => {
  // 测试将等待 beforeAll 完成
  await expect(Promise.resolve("test")).resolves.toBe("test");
});
```

## 嵌套钩子

钩子可以嵌套，并会以适当顺序运行：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test";

beforeAll(() => console.log("文件 beforeAll"));
afterAll(() => console.log("文件 afterAll"));

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

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

    test("嵌套测试", () => {
      console.log("测试运行中");
    });
  });
});
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
// 输出顺序：
// 文件 beforeAll
// 外层 beforeAll
// 内层 beforeAll
// 外层 beforeEach
// 内层 beforeEach
// 测试运行中
// 内层 afterEach
// 外层 afterEach
// 内层 afterAll
// 外层 afterAll
// 文件 afterAll
```

## 错误处理

如果生命周期钩子抛出错误，将影响测试执行：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, test } from "bun:test";

beforeAll(() => {
  // 如果这里抛出，当前作用域的所有测试都将跳过
  throw new Error("设置失败");
});

test("此测试将被跳过", () => {
  // 因为 beforeAll 失败，此测试不会运行
});
```

更好的错误处理方式：

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, test, expect } from "bun:test";

beforeAll(async () => {
  try {
    await setupDatabase();
  } catch (error) {
    console.error("数据库设置失败：", error);
    throw error; // 重新抛出以使测试套件失败
  }
});
```

## 最佳实践

### 保持钩子简单

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 好：简单、专注的设置
beforeEach(() => {
  clearLocalStorage();
  resetMocks();
});

// 避免：钩子中复杂逻辑
beforeEach(async () => {
  // 过于复杂的逻辑使测试难以调试
  const data = await fetchComplexData();
  await processData(data);
  await setupMultipleServices(data);
});
```

### 使用合适的作用域

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 好：文件级别的共享资源设置
beforeAll(async () => {
  await startTestServer();
});

// 好：测试级别的测试特定状态设置
beforeEach(() => {
  user = createTestUser();
});
```

### 清理资源

```ts title="test.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterAll, afterEach } from "bun:test";

afterEach(() => {
  // 每个测试后的清理
  document.body.innerHTML = "";
  localStorage.clear();
});

afterAll(async () => {
  // 清理昂贵资源
  await closeDatabase();
  await stopServer();
});
```
