Skip to main content
测试运行器支持以下生命周期钩子。这对于加载测试夹具、模拟数据以及配置测试环境非常有用。
钩子描述
beforeAll在所有测试之前运行一次。
beforeEach在每个测试之前运行。
afterEach在每个测试之后运行。
afterAll在所有测试之后运行一次。
onTestFinished在单个测试完成后运行(在所有 afterEach 之后)。

每个测试的设置与拆卸

使用 beforeEachafterEach 执行每个测试的设置与拆卸逻辑。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { beforeEach, afterEach, test } from "bun:test";

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

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

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

每个作用域的设置与拆卸

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

限定在 describe 块内

要将钩子限定在特定的 describe 块内:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { describe, beforeAll, afterAll, test } from "bun:test";

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

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

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

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

限定在测试文件内

要将钩子限定在整个测试文件范围:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { describe, beforeAll, afterAll, test } from "bun:test";

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

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

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

onTestFinished

使用 onTestFinished 来在单个测试完成后运行回调。它会在所有 afterEach 钩子之后运行。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { test, onTestFinished } from "bun:test";

test("测试后的清理", () => {
  onTestFinished(() => {
    // 在所有 afterEach 钩子之后运行
    console.log("测试完成");
  });
});
并发测试不支持该钩子;请改用 test.serial

全局设置与拆卸

要将钩子限定于整个多文件测试运行,请在单独文件中定义钩子。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79setup.ts
import { beforeAll, afterAll } from "bun:test";

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

afterAll(() => {
  // 全局拆卸
  console.log("全局测试拆卸");
  // 关闭数据库连接、停止服务器等
});
然后使用 --preload 在任何测试文件之前运行设置脚本。
terminal
bun test --preload ./setup.ts
为了避免每次运行测试时都输入 --preload,可以将其添加到你的 bunfig.toml
bunfig.toml
[test]
preload = ["./setup.ts"]

实用示例

数据库设置

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79database-setup.ts
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 服务器设置

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79server-setup.ts
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);
});

模拟设置

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79mock-setup.ts
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();
});

异步生命周期钩子

所有生命周期钩子都支持异步函数:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
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");
});

嵌套钩子

钩子可以嵌套,并会以适当顺序运行:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
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("测试运行中");
    });
  });
});
// 输出顺序:
// 文件 beforeAll
// 外层 beforeAll
// 内层 beforeAll
// 外层 beforeEach
// 内层 beforeEach
// 测试运行中
// 内层 afterEach
// 外层 afterEach
// 内层 afterAll
// 外层 afterAll
// 文件 afterAll

错误处理

如果生命周期钩子抛出错误,将影响测试执行:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { beforeAll, test } from "bun:test";

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

test("此测试将被跳过", () => {
  // 因为 beforeAll 失败,此测试不会运行
});
更好的错误处理方式:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { beforeAll, test, expect } from "bun:test";

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

最佳实践

保持钩子简单

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
// 好:简单、专注的设置
beforeEach(() => {
  clearLocalStorage();
  resetMocks();
});

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

使用合适的作用域

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
// 好:文件级别的共享资源设置
beforeAll(async () => {
  await startTestServer();
});

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

清理资源

https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79test.ts
import { afterAll, afterEach } from "bun:test";

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

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