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.
测试运行器支持以下生命周期钩子。这对于加载测试夹具、模拟数据以及配置测试环境非常有用。
| 钩子 | 描述 |
|---|
beforeAll | 在所有测试之前运行一次。 |
beforeEach | 在每个测试之前运行。 |
afterEach | 在每个测试之后运行。 |
afterAll | 在所有测试之后运行一次。 |
onTestFinished | 在单个测试完成后运行(在所有 afterEach 之后)。 |
每个测试的设置与拆卸
使用 beforeEach 和 afterEach 执行每个测试的设置与拆卸逻辑。
test.tsimport { beforeEach, afterEach, test } from "bun:test";
beforeEach(() => {
console.log("运行测试。");
});
afterEach(() => {
console.log("测试完成。");
});
// 测试...
test("示例测试", () => {
// 此测试将在它之前运行 beforeEach
// 并在它之后运行 afterEach
});
每个作用域的设置与拆卸
使用 beforeAll 和 afterAll 执行每个作用域的设置与拆卸逻辑。作用域由钩子定义的位置决定。
限定在 describe 块内
要将钩子限定在特定的 describe 块内:
test.tsimport { describe, beforeAll, afterAll, test } from "bun:test";
describe("测试组", () => {
beforeAll(() => {
// 此 describe 块的设置
console.log("设置测试组");
});
afterAll(() => {
// 此 describe 块的拆卸
console.log("拆卸测试组");
});
test("测试 1", () => {
// 测试实现
});
test("测试 2", () => {
// 测试实现
});
});
限定在测试文件内
要将钩子限定在整个测试文件范围:
test.tsimport { describe, beforeAll, afterAll, test } from "bun:test";
beforeAll(() => {
// 整个文件的设置
console.log("设置测试文件");
});
afterAll(() => {
// 整个文件的拆卸
console.log("拆卸测试文件");
});
describe("测试组", () => {
test("测试 1", () => {
// 测试实现
});
});
onTestFinished
使用 onTestFinished 来在单个测试完成后运行回调。它会在所有 afterEach 钩子之后运行。
test.tsimport { test, onTestFinished } from "bun:test";
test("测试后的清理", () => {
onTestFinished(() => {
// 在所有 afterEach 钩子之后运行
console.log("测试完成");
});
});
并发测试不支持该钩子;请改用 test.serial。
全局设置与拆卸
要将钩子限定于整个多文件测试运行,请在单独文件中定义钩子。
setup.tsimport { beforeAll, afterAll } from "bun:test";
beforeAll(() => {
// 全局设置
console.log("全局测试设置");
// 初始化数据库连接、启动服务器等
});
afterAll(() => {
// 全局拆卸
console.log("全局测试拆卸");
// 关闭数据库连接、停止服务器等
});
然后使用 --preload 在任何测试文件之前运行设置脚本。
bun test --preload ./setup.ts
为了避免每次运行测试时都输入 --preload,可以将其添加到你的 bunfig.toml:
[test]
preload = ["./setup.ts"]
实用示例
数据库设置
database-setup.tsimport { 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 服务器设置
server-setup.tsimport { 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);
});
模拟设置
mock-setup.tsimport { 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();
});
异步生命周期钩子
所有生命周期钩子都支持异步函数:
test.tsimport { 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");
});
嵌套钩子
钩子可以嵌套,并会以适当顺序运行:
test.tsimport { 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
错误处理
如果生命周期钩子抛出错误,将影响测试执行:
test.tsimport { beforeAll, test } from "bun:test";
beforeAll(() => {
// 如果这里抛出,当前作用域的所有测试都将跳过
throw new Error("设置失败");
});
test("此测试将被跳过", () => {
// 因为 beforeAll 失败,此测试不会运行
});
更好的错误处理方式:
test.tsimport { beforeAll, test, expect } from "bun:test";
beforeAll(async () => {
try {
await setupDatabase();
} catch (error) {
console.error("数据库设置失败:", error);
throw error; // 重新抛出以使测试套件失败
}
});
最佳实践
保持钩子简单
test.ts// 好:简单、专注的设置
beforeEach(() => {
clearLocalStorage();
resetMocks();
});
// 避免:钩子中复杂逻辑
beforeEach(async () => {
// 过于复杂的逻辑使测试难以调试
const data = await fetchComplexData();
await processData(data);
await setupMultipleServices(data);
});
使用合适的作用域
test.ts// 好:文件级别的共享资源设置
beforeAll(async () => {
await startTestServer();
});
// 好:测试级别的测试特定状态设置
beforeEach(() => {
user = createTestUser();
});
清理资源
test.tsimport { afterAll, afterEach } from "bun:test";
afterEach(() => {
// 每个测试后的清理
document.body.innerHTML = "";
localStorage.clear();
});
afterAll(async () => {
// 清理昂贵资源
await closeDatabase();
await stopServer();
});