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);
});
モックのセットアップ
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 runafterAll がスキップされたテスト内で例外がスローされないようにする
State leakクリーンアップが不足している適切な afterEach を追加する

リファレンスを読み込むタイミング

次の場合は references/preload-scripts.md を読み込みます。

  • 複雑なグローバルセットアップ
  • 複数のプリロードファイル

次の場合は references/fixtures.md を読み込みます。

  • 再利用可能なテストフィクスチャ
  • ファクトリーパターン

コメント