mcpField
B
bun-test-lifecycle

bun-test-lifecycle

github.com/secondsky测试与质量240235 次安装5 周前更新
代码仓库

关于 bun-test-lifecycle

bun-test-lifecycle 是用于组织 Bun 测试准备与清理工作的技能,介绍生命周期钩子、执行顺序、预加载脚本和可复用测试模式。

主要功能

  1. 01生命周期钩子的用法
  2. 02钩子执行顺序
  3. 03异步准备与超时设置
  4. 04使用预加载脚本进行全局设置
  5. 05数据库、服务器与模拟模式

适用场景

  1. 01为测试文件准备共享状态
  2. 02在每项测试前填充数据
  3. 03启动和关闭测试服务器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill bun-test-lifecycle

安装后在 Claude Code 中输入/bun-test-lifecycle确认已加载。

详细介绍

Bun 测试生命周期

Bun 支持与 Jest 兼容的生命周期钩子,用于测试的设置和清理。

生命周期钩子

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

异步钩子

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

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

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

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

钩子超时

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

预加载脚本

使用 --preload 为所有测试文件进行全局设置:

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;
});

共享测试夹具

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

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

// test file
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异步设置缓慢增加超时时间
Hook not called作用域错误检查钩子的位置
Cleanup not run跳过了 afterAll确保测试中没有抛出错误
State leak缺少清理添加适当的 afterEach

何时加载参考文档

遇到以下情况时,加载 references/preload-scripts.md:

  • 复杂的全局设置
  • 多个预加载文件

遇到以下情况时,加载 references/fixtures.md:

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

评论

支持 Markdown