SuperAPI AI 工具集

bun-test-lifecycle

测试与质量

查看项目

更新于 10-15 14:58

技能介绍

用途

Bun Test Lifecycle 用于在 Bun 测试中管理测试的生命周期钩子,实现测试的初始化与清理。Bun 支持与 Jest 兼容的生命周期钩子,可用于测试的 setup 和 teardown。

主要能力

生命周期钩子

可从 "bun:test" 导入 test、expect、beforeAll、afterAll、beforeEach、afterEach。

  • beforeAll:在文件中所有测试之前运行一次。
  • afterAll:在文件中所有测试之后运行一次。
  • beforeEach:在每个测试之前运行。
  • afterEach:在每个测试之后运行。

示例代码:

import { test, expect, beforeAll, afterAll, beforeEach, afterEach } from "bun:test";

let db: Database;
let testData: any;

// Runs once before all tests in file
beforeAll(async () => {
  db = await Database.connect();
});

// Runs once after all tests in file
afterAll(async () => {
  await db.close();
});

// Runs before each test
beforeEach(() => {
  testData = { id: 1, name: "test" };
});

// Runs after each test
afterEach(() => {
  testData = null;
});

test("example", () => {
  expect(testData.name).toBe("test");
});

钩子执行顺序

beforeAll
├── beforeEach
│   └── test 1
│   └── afterEach
├── beforeEach
│   └── test 2
│   └── afterEach
afterAll

嵌套 Describe 钩子

import { describe, test, beforeAll, beforeEach, afterEach, afterAll } from "bun:test";

describe("outer", () => {
  beforeAll(() => console.log("1. outer beforeAll"));
  afterAll(() => console.log("6. outer afterAll"));
  beforeEach(() => console.log("2. outer beforeEach"));
  afterEach(() => console.log("5. outer afterEach"));

  describe("inner", () => {
    beforeEach(() => console.log("3. inner beforeEach"));
    afterEach(() => console.log("4. inner afterEach"));

    test("test", () => {
      console.log("test runs here");
    });
  });
});

// Output:
// 1. outer beforeAll
// 2. outer beforeEach
// 3. inner beforeEach
// test runs here
// 4. inner afterEach
// 5. outer afterEach
// 6. outer afterAll

异步钩子

钩子支持 async 函数:

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

beforeEach(async () => {
  await seedTestData();
});

afterEach(async () => {
  await clearTestData();
});

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

钩子超时

可为较慢的 setup 设置超时:

// Set timeout for slow setup
beforeAll(async () => {
  await slowSetup();
}, 30000); // 30 seconds

预加载脚本(Preload Scripts)

使用 --preload 在所有测试文件之间进行全局 setup:

bun test --preload ./setup.ts

setup.ts:

import { beforeAll, afterAll } from "bun:test";

// Global setup
beforeAll(() => {
  console.log("Global setup runs before all test files");
});

// Global teardown
afterAll(() => {
  console.log("Global teardown runs after all test files");
});

// Set global variables
globalThis.testConfig = {
  apiUrl: "http://localhost:3000",
};

在 bunfig.toml 中配置:

[test]
preload = ["./test/setup.ts"]

常见模式

数据库设置

import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";

let db: Database;

beforeAll(async () => {
  db = await Database.connect(process.env.TEST_DB_URL);
  await db.migrate();
});

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

beforeEach(async () => {
  await db.beginTransaction();
});

afterEach(async () => {
  await db.rollback(); // Reset state
});

服务器设置

import { beforeAll, afterAll } from "bun:test";

let server: Server;
let baseUrl: string;

beforeAll(async () => {
  server = Bun.serve({
    port: 0, // Random available port
    fetch: app.fetch,
  });
  baseUrl = `http://localhost:${server.port}`;
});

// Bun 1.2+: server.stop() returns a Promise that resolves once in-flight
// connections have closed — await it so the test process doesn't exit early.
afterAll(async () => {
  await server.stop();
});

test("api works", async () => {
  const res = await fetch(`${baseUrl}/api/health`);
  expect(res.ok).toBe(true);
});

Mock 设置

import { beforeEach, afterEach, spyOn } from "bun:test";

let fetchSpy: ReturnType<typeof spyOn>;

beforeEach(() => {
  fetchSpy = spyOn(global, "fetch").mockResolvedValue(
    new Response(JSON.stringify({ ok: true }))
  );
});

afterEach(() => {
  fetchSpy.mockRestore();
});

环境变量

import { beforeAll, afterAll } from "bun:test";

const originalEnv = process.env;

beforeAll(() => {
  process.env = {
    ...originalEnv,
    NODE_ENV: "test",
    API_KEY: "test-key",
  };
});

afterAll(() => {
  process.env = originalEnv;
});

共享夹具(Shared Fixtures)

fixtures.ts:

export async function createTestUser() {
  return { id: 1, name: "Test User" };
}

export async function cleanupTestUser(user: any) {
  // cleanup logic
}

测试文件:

import { createTestUser, cleanupTestUser } from "./fixtures";

let user: any;

beforeEach(async () => {
  user = await createTestUser();
});

afterEach(async () => {
  await cleanupTestUser(user);
});

钩子错误

如果钩子抛出异常,该 describe 块中的所有测试都会失败:

beforeAll(() => {
  throw new Error("Setup failed");
});

// All tests in this file will fail
test("will fail", () => {
  expect(true).toBe(true);
});

常见错误

错误 原因 修复
beforeAll timeout 异步 setup 过慢 增加超时时间
Hook not called 作用域错误 检查钩子放置位置
Cleanup not run afterAll 被跳过 确保测试中没有抛出异常
State leak 缺少清理 添加适当的 afterEach

何时加载参考文档

在以下情况加载 references/preload-scripts.md:

  • 复杂的全局 setup
  • 多个 preload 文件

在以下情况加载 references/fixtures.md:

  • 可复用的测试夹具
  • 工厂模式

适用场景

适用于使用 Bun 测试框架、需要管理测试前后初始化与清理逻辑的项目,例如数据库连接与迁移、测试服务器启动与关闭、Mock 设置与还原、环境变量切换、共享测试夹具等场景。

限制

资料未提供关于该技能的平台限制、收费方式、价格或效果保证等信息。