コンテンツにスキップ

第21章: Vitest Pool によるエッジ完全互換テスト(実践チュートリアル)

通常、JestやVitestをNode.js環境で動かすと、以下のような「エッジ特有の環境差異」によるトラブルが発生します。

  • window や Node.js の fs / Buffer がエッジ本番で使えず落ちる
  • D1(SQLite)やKVのモックを書くのが大変で、テストが形骸化する

@cloudflare/vitest-pool-workers は、オープンソースの本物エッジランタイム(workerd)内でテストを実行するため、本番と100%同じ挙動でD1やKVのクエリを検証 できます。


本チュートリアルのゴール

  • @cloudflare/vitest-pool-workersvitest の導入
  • vitest.config.ts によるWorkers設定の読み込み
  • D1データベースのマイグレーション自動実行と、Hono APIの統合テスト作成
  • npx vitest run による高速テスト実行

Step 1: 必要なパッケージのインストール

Terminal window
npm install --save-dev vitest @cloudflare/vitest-pool-workers

Step 2: vitest.config.ts の作成

プロジェクトルートに vitest.config.ts を作成します。

vitest.config.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
// wrangler.jsonc(または wrangler.toml)のパスを指定
wrangler: { configPath: './wrangler.jsonc' },
},
},
},
});

tsconfig.json にテスト用の型定義を追加します。

{
"compilerOptions": {
"types": [
"@cloudflare/workers-types/2023-07-01",
"@cloudflare/vitest-pool-workers"
]
}
}

Step 3: テスト対象のAPIコード(src/index.ts

D1にアクセスするシンプルなHono APIです。

src/index.ts
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
// ヘルスチェック
app.get('/api/health', (c) => {
return c.json({ status: 'ok' });
});
// ユーザー作成(D1 INSERT)
app.post('/api/users', async (c) => {
const { name, email } = await c.req.json<{ name: string; email: string }>();
if (!name || !email) {
return c.json({ error: 'Name and email are required' }, 400);
}
const user = await c.env.DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *'
)
.bind(name, email)
.first();
return c.json(user, 201);
});
export default app;

Step 4: 統合テストコードの作成(test/index.spec.ts

cloudflare:test から env を直接インポートし、テスト用エッジランタイム内で実際のSQLを実行して検証します。

test/index.spec.ts
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect, beforeAll } from 'vitest';
import worker from '../src/index';
describe('エッジ統合テスト (workerd ランタイム)', () => {
// テスト開始前にD1のテーブルを初期化
beforeAll(async () => {
await env.DB.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
`);
});
it('GET /api/health が 200 OK を返すこと', async () => {
const request = new Request('http://localhost/api/health');
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(200);
const body: any = await response.json();
expect(body.status).toBe('ok');
});
it('POST /api/users でD1に実データが保存されること', async () => {
const request = new Request('http://localhost/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '山田太郎', email: 'yamada@example.com' }),
});
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(201);
const createdUser: any = await response.json();
expect(createdUser.name).toBe('山田太郎');
// D1データベースから直接SELECTして書き込みを二重検証!
const dbRecord = await env.DB.prepare('SELECT * FROM users WHERE email = ?')
.bind('yamada@example.com')
.first();
expect(dbRecord).toBeDefined();
expect(dbRecord?.name).toBe('山田太郎');
});
it('バリデーションエラー時に 400 を返すこと', async () => {
const request = new Request('http://localhost/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '' }),
});
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(400);
});
});

Step 5: テストの実行

Terminal window
npx vitest run

出力例:

✓ test/index.spec.ts (3 tests) 24ms
✓ エッジ統合テスト (workerd ランタイム) > GET /api/health が 200 OK を返すこと
✓ エッジ統合テスト (workerd ランタイム) > POST /api/users でD1に実データが保存されること
✓ エッジ統合テスト (workerd ランタイム) > バリデーションエラー時に 400 を返すこと
Test Files 1 passed (1)
Tests 3 passed (3)
Duration 185ms

わずか0.2秒以下で、本物エッジDBに対する統合テストが完了します。


まとめ

  • モック不要: D1やKVを複雑にモックすることなく、env.DB に対して本番同様にクエリ可能。
  • CI/CD自動化: GitHub Actions に組み込むことで、バグを100%事前に検知可能。

💡 用語解説コラム

[!NOTE] workerd (ワーカーディー)
Cloudflare WorkersのコアとなっているオープンソースのJavaScript/Wasmランタイム。Node.jsではなくこの本物ランタイム上でテストを動かすことで環境差異のバグを根絶します。