cloudflare-workers-testing について
Cloudflare Workers 開発者向けの Vitest テストガイドです。Miniflare で Workers バインディングと分離ストレージを使う単体・統合テストを解説します。
主な機能
- 01Miniflare でのテスト実行
- 02D1、KV、R2 などのバインディングをテスト
- 03実行コンテキストとバックグラウンド処理を検証
- 04カバレッジ追跡の設定
- 05単体・統合テストのパターンを解説
活用シーン
- 01D1 を使う API のテスト
- 02KV キャッシュ動作の検証
- 03R2 ファイルアップロードのテスト
- 04Durable Objects の状態確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill cloudflare-workers-testingインストール後、Claude Code で/cloudflare-workers-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/binding-mocks.mdcoverage-optimization.mdintegration-testing.mdtroubleshooting.mdvitest-setup.mdscripts/run-tests.shsetup-vitest.shtemplates/basic-test.tsbinding-mock-test.tsvitest-config.ts詳細
Vitest を使用した Cloudflare Workers のテスト
ステータス:✅ 本番環境で使用可能 | 最終確認日:2025-01-27 Vitest:^2.0.0 | @cloudflare/vitest-pool-workers:0.7.2 | Miniflare:最新
目次
- Workers のテストとは?
- 2025 年の新機能
- クイックスタート(5 分)
- 重要なルール
- 基本概念
- 主な使用例 5 選
- ベストプラクティス
- 防止できる主なエラー 8 選
- リファレンスを読み込むタイミング
Workers のテストとは?
Vitest と @cloudflare/vitest-pool-workers を使用して Cloudflare Workers をテストすると、すべてのバインディング(D1、KV、R2、Durable Objects、Queues、AI)を完全にサポートする実際の Workers 環境で、ユニットテストや統合テストを作成して実行できます。テストはローカル開発では Miniflare 上で実行され、CI/CD では実際の Workers ランタイムの動作で実行できます。
主な機能:バインディングのモック、実行コンテキストのテスト、エッジランタイムのシミュレーション、カバレッジの追跡、高速なテスト実行。
2025 年の新機能
@cloudflare/vitest-pool-workers 0.7.2(2025 年 1 月):
- 破壊的変更:Miniflare v3 → Node.js 20 以降が必要
- 新機能:env/ctx にアクセスするための
cloudflare:testモジュール - 改善:バインディング用の分離ストレージが高速化
- 修正:Worker 間のサービスバインディングが正常に動作
- 追加:Vectorize および Workers AI バインディングのサポート
以前のバージョンからの移行:
# Update dependencies
bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2
# Update vitest.config.ts (new pool configuration format)
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
miniflare: { compatibilityDate: '2025-01-27' }
}
}
}
});
クイックスタート(5 分)
1. 依存関係をインストールする
bun add -D vitest @cloudflare/vitest-pool-workers
2. vitest.config.ts を作成する
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
miniflare: {
compatibilityDate: '2025-01-27',
compatibilityFlags: ['nodejs_compat']
}
}
}
}
});
3. 最初のテストを書く
import { describe, it, expect } from 'vitest';
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import worker from '../src/index';
describe('Worker', () => {
it('responds with 200', async () => {
const request = new Request('http://example.com/');
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(200);
});
});
4. テストを実行する
bun test
# or
bunx vitest
重要なルール
1. env へのアクセスには必ず cloudflare:test を使用する
✅ 正しい例:
import { env } from 'cloudflare:test';
it('queries D1', async () => {
const result = await env.DB.prepare('SELECT * FROM users').all();
expect(result.results).toHaveLength(0); // Fresh isolated DB per test
});
❌ 誤った例:
// Don't manually create env object
const env = { DB: mockDB }; // ❌ Won't use real D1 binding
理由:cloudflare:test は、wrangler.jsonc で設定された実際のバインディングを提供し、テストごとにストレージを分離します。
2. 実行コンテキストの完了を必ず待つ
✅ 正しい例:
it('handles async operations', async () => {
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ Ensures ctx.waitUntil completes
expect(response.status).toBe(200);
});
❌ 誤った例:
it('missing wait', async () => {
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// ❌ Missing waitOnExecutionContext - ctx.waitUntil tasks may not complete
expect(response.status).toBe(200);
});
理由:Workers はバックグラウンドタスク(ロギング、分析など)に ctx.waitUntil() を使用します。完了を待たないと、テスト内でこれらのタスクが完了しないことがあります。
3. 各テストには分離されたストレージが割り当てられる
✅ 正しい例:
describe('KV Operations', () => {
it('test 1: writes to KV', async () => {
await env.CACHE.put('key', 'value1');
const val = await env.CACHE.get('key');
expect(val).toBe('value1'); // ✅ Isolated
});
it('test 2: clean state', async () => {
const val = await env.CACHE.get('key');
expect(val).toBeNull(); // ✅ Test 1's data doesn't leak here
});
});
理由:各テストは、新しいバインディングストレージを使用して実行されます(分離は自動的に行われます)。
4. バインディングには Wrangler の設定を使用する
✅ 正しい例:
// vitest.config.ts
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' } // ✅ Reads bindings from wrangler
}
}
}
});
❌ 誤った例:
// vitest.config.ts
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
// ❌ No wrangler config - bindings won't be available
miniflare: { compatibilityDate: '2025-01-27' }
}
}
}
});
理由:Wrangler の設定で、すべてのバインディング(D1、KV、R2 など)を定義します。設定がないと、env は空になります。
5. 互換性日付を一致させる
✅ 正しい例:
// vitest.config.ts
miniflare: {
compatibilityDate: '2025-01-27' // ✅ Matches wrangler.jsonc
}
// wrangler.jsonc
{
"compatibility_date": "2025-01-27"
}
理由:テスト環境が本番ランタイムの動作と一致するようにします。
基本概念
バインディングのテストパターン
D1 データベース:
import { env } from 'cloudflare:test';
it('queries D1', async () => {
// Insert test data
await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Alice').run();
// Query
const result = await env.DB.prepare('SELECT * FROM users WHERE name = ?').bind('Alice').first();
expect(result?.name).toBe('Alice');
});
KV ネームスペース:
it('reads from KV', async () => {
await env.CACHE.put('test-key', 'test-value');
const value = await env.CACHE.get('test-key');
expect(value).toBe('test-value');
});
R2 バケット:
it('uploads to R2', async () => {
await env.BUCKET.put('file.txt', 'Hello World');
const object = await env.BUCKET.get('file.txt');
expect(await object?.text()).toBe('Hello World');
});
Durable Objects:
it('interacts with Durable Object', async () => {
const id = env.COUNTER.idFromName('test-counter');
const stub = env.COUNTER.get(id);
const response = await stub.fetch('http://fake/increment');
const data = await response.json();
expect(data.count).toBe(1);
});
ユニットテストと統合テスト
ユニットテスト(単一の関数):
import { validateInput } from '../src/utils/validator';
it('validates input', () => {
const result = validateInput({ name: 'Alice', age: 30 });
expect(result.valid).toBe(true);
});
統合テスト(fetch ハンドラー全体):
import worker from '../src/index';
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
it('handles full request flow', async () => {
const request = new Request('http://example.com/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' })
});
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(201);
const user = await response.json();
expect(user.name).toBe('Alice');
});
カバレッジの設定
vitest.config.ts に追加します:
export default defineWorkersConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80
}
}
}
});
カバレッジ付きで実行します:
bunx vitest run --coverage
主な使用例 5 選
1. D1 を使用した API エンドポイントのテスト
it('creates user via API', async () => {
const request = new Request('http://example.com/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Bob', email: '[email protected]' })
});
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(201);
// Verify DB insert
const user = await env.DB.prepare('SELECT * FROM users WHERE email = ?')
.bind('[email protected]')
.first();
expect(user?.name).toBe('Bob');
});
2. KV を使用したキャッシュのテスト
it('caches API responses', async () => {
// First request (cache miss)
const req1 = new Request('http://example.com/api/data');
const ctx1 = createExecutionContext();
const res1 = await worker.fetch(req1, env, ctx1);
await waitOnExecutionContext(ctx1);
expect(res1.headers.get('X-Cache')).toBe('MISS');
// Second request (cache hit)
const req2 = new Request('http://example.com/api/data');
const ctx2 = createExecutionContext();
const res2 = await worker.fetch(req2, env, ctx2);
await waitOnExecutionContext(ctx2);
expect(res2.headers.get('X-Cache')).toBe('HIT');
});
3. R2 へのファイルアップロードのテスト
it('handles file upload', async () => {
const formData = new FormData();
formData.append('file', new Blob(['test content'], { type: 'text/plain' }), 'test.txt');
const request = new Request('http://example.com/upload', {
method: 'POST',
body: formData
});
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(200);
// Verify R2 upload
const object = await env.BUCKET.get('test.txt');
expect(await object?.text()).toBe('test content');
});
4. Durable Objects の状態のテスト
it('maintains counter state', async () => {
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);
// Increment 3 times
for (let i = 0; i < 3; i++) {
await stub.fetch('http://fake/increment');
}
// Verify state
const response = await stub.fetch('http://fake/value');
const data = await response.json();
expect(data.count).toBe(3);
});
5. キューコンシューマーのテスト
it('processes queue messages', async () => {
const messages = [
{ id: '1', body: { action: 'email', to: '[email protected]' }, timestamp: new Date() }
];
// Simulate queue batch
await worker.queue(
{
queue: 'my-queue',
messages,
retryAll: () => {},
ackAll: () => {}
},
env
);
// Verify processing (check DB, logs, etc.)
const log = await env.DB.prepare('SELECT * FROM email_log WHERE id = ?').bind('1').first();
expect(log?.status).toBe('sent');
});
ベストプラクティス
✅ 推奨事項
-
内容がわかるテスト名を付ける:
it('returns 404 when user not found', async () => {}); it('validates email format before saving', async () => {}); -
エラーケースをテストする:
it('returns 400 for invalid JSON', async () => { const request = new Request('http://example.com/api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: 'invalid json' }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); expect(response.status).toBe(400); }); -
関連するテストをグループ化する:
describe('User API', () => { describe('POST /users', () => { it('creates user with valid data', async () => {}); it('rejects duplicate email', async () => {}); it('validates required fields', async () => {}); }); }); -
セットアップには beforeEach を使用する:
describe('Database tests', () => { beforeEach(async () => { // Seed test data await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Test User').run(); }); it('queries users', async () => { const result = await env.DB.prepare('SELECT * FROM users').all(); expect(result.results).toHaveLength(1); }); }); -
現実的なシナリオをテストする:
it('handles concurrent requests', async () => { const requests = Array(10).fill(null).map(() => worker.fetch(new Request('http://example.com/'), env, createExecutionContext()) ); const responses = await Promise.all(requests); expect(responses.every(r => r.status === 200)).toBe(true); });
❌ 避けること
-
テスト間で状態を共有しない:
// ❌ BAD: Leaky state let counter = 0; it('test 1', () => { counter++; }); it('test 2', () => { expect(counter).toBe(1); }); // Fragile! // ✅ GOOD: Isolated it('test 1', () => { const counter = 0; counter++; }); it('test 2', () => { const counter = 0; /* fresh state */ }); -
完了を待ち忘れない:
// ❌ BAD const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(200); // ctx.waitUntil not finished // ✅ GOOD const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); expect(response.status).toBe(200); -
URL をハードコードしない:
// ❌ BAD const request = new Request('http://example.com/test'); // ✅ GOOD const request = new Request('http://fake-host/test'); // Host doesn't matter in tests -
実装の詳細をテストしない:
// ❌ BAD: Testing internals expect(worker.privateHelperFunction).toBeDefined(); // ✅ GOOD: Testing behavior const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(200);
防止できる主なエラー 8 選
1. ❌ ReferenceError: env is not defined
原因:cloudflare:test から env をインポートしていません。
修正方法:
import { env } from 'cloudflare:test'; // ✅ Add this import
予防策:env へのアクセスには必ず cloudflare:test モジュールを使用します。
2. ❌ TypeError: Cannot read property 'DB' of undefined
原因:vitest.config.ts で wrangler.jsonc が読み込まれていません。
修正方法:
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' } // ✅ Add this
}
}
}
});
予防策:必ず Vitest の設定で Wrangler のパスを設定してください。
3. ❌ Error: D1_ERROR: no such table: users
原因:テストで D1 データベースのスキーマが適用されていません。
修正方法:
// Option 1: Seed in beforeEach
beforeEach(async () => {
await env.DB.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`);
});
// Option 2: Use migrations (load from file)
beforeEach(async () => {
const schema = await fs.readFile('./migrations/schema.sql', 'utf-8');
await env.DB.exec(schema);
});
予防策:各テストの前にスキーマを作成するか、共有セットアップを使用してください。
4. ❌ Error: ctx.waitUntil tasks did not complete
原因:await waitOnExecutionContext(ctx) がありません。
修正方法:
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ Add this
予防策:テストでは必ず実行コンテキストの完了を待ってください。
5. ❌ Error: SELF is not defined
原因:ワーカーを直接インポートする代わりに、古い SELF.fetch() パターンを使用しています。
修正方法:
// ❌ OLD (vitest-pool-workers <0.5)
import { SELF } from 'cloudflare:test';
await SELF.fetch(request);
// ✅ NEW (vitest-pool-workers ≥0.7)
import worker from '../src/index';
await worker.fetch(request, env, ctx);
予防策:ワーカーを直接インポートしてください(最新のパターン)。
6. ❌ Error: KV.get() returned data from previous test
原因:ストレージが共有されていると思い込んでいます(実際には共有されませんが、テスト間のデータ漏れを示している可能性があります)。
修正方法:各テストは分離されています。このエラーが発生する場合は、次の点を確認してください:
// ❌ Test pollution (shared variable)
let cache = {};
it('test 1', () => { cache.key = 'value'; });
it('test 2', () => { expect(cache.key).toBeUndefined(); }); // Fails!
// ✅ Proper isolation
it('test 1', async () => { await env.CACHE.put('key', 'value1'); });
it('test 2', async () => { const val = await env.CACHE.get('key'); expect(val).toBeNull(); });
予防策:テストデータに共有変数を使用しないでください。
7. ❌ TypeError: env.BUCKET.put is not a function
原因:wrangler.jsonc で R2 バインディングが設定されていません。
修正方法:
// wrangler.jsonc
{
"r2_buckets": [
{ "binding": "BUCKET", "bucket_name": "test-bucket" }
]
}
予防策:Wrangler の設定で、すべてのバインディングを定義してください。
8. ❌ Error: Pool 'workers' is not supported
原因:@cloudflare/vitest-pool-workers の依存関係がありません。
修正方法:
bun add -D @cloudflare/vitest-pool-workers
予防策:Workers のテスト用にプールパッケージをインストールしてください。
リファレンスを読み込むタイミング
詳細な専門情報については、リファレンスファイルを読み込んでください。
次の場合は references/vitest-setup.md を読み込んでください:
- Vitest をゼロからセットアップする
- カスタムプールオプションを設定する
- Miniflare の設定をトラブルシューティングする
- 古いバージョンの vitest-pool-workers から移行する
次の場合は references/binding-mocks.md を読み込んでください:
- 特定のバインディング(D1、KV、R2、DO、Queues、AI、Vectorize)をテストする
- サービスバインディング(ワーカー間)をモックする
- バインディング用のテストフィクスチャを作成する
- 分離されたストレージの動作を理解する
次の場合は references/integration-testing.md を読み込んでください:
- リクエスト/レスポンスの包括的なテストを作成する
- 複数ステップのワークフローをテストする
- 本番環境のシナリオをシミュレートする
- WebSocket またはストリーミングレスポンスをテストする
次の場合は references/coverage-optimization.md を読み込んでください:
- カバレッジのしきい値を設定する
- テストされていないコードパスを特定する
- テストスイートのパフォーマンスを最適化する
- カバレッジレポーターを設定する
次の場合は references/troubleshooting.md を読み込んでください:
- 失敗するテストをデバッグする
- バインディングエラーを解決する
- タイムアウトの問題を修正する
- エラーメッセージを理解する
次の用途には templates/vitest-config.ts を読み込んでください:
vitest.config.tsの完全な例- 高度な設定オプション
- 複数の Wrangler 環境
次の用途には templates/basic-test.ts を読み込んでください:
- テストファイル構成のテンプレート
- 一般的なテストパターン
beforeEach/afterEachの例
次の用途には templates/binding-mock-test.ts を読み込んでください:
- バインディング固有のテスト例
- D1、KV、R2、DO のテストパターン
- Queue と AI のテスト例
次の用途には scripts/setup-vitest.sh を読み込んでください:
- Vitest の自動インストール
- プロジェクト設定スクリプト
次の用途には scripts/run-tests.sh を読み込んでください:
- CI/CD でのテスト実行
- カバレッジレポートの自動化
関連する Cloudflare プラグイン
サービス固有のテストパターンについては、次を読み込んでください:
- cloudflare-d1 - D1 データベースのテスト、マイグレーション、データ投入
- cloudflare-kv - KV ネームスペースのテスト、TTL の検証
- cloudflare-r2 - R2 バケットのテスト、ファイルのアップロード/ダウンロード
- cloudflare-durable-objects - DO のテスト、WebSocket のテスト
- cloudflare-queues - Queue のテスト、バッチ処理
- cloudflare-workers-ai - AI モデルのテスト、推論のモック
このスキルでは、すべてのバインディングの種類と Workers の機能に適用できる、横断的な Workers のテストパターンを扱います。
質問がありますか? references/troubleshooting.md を読み込むか、/workers-debug コマンドを使用して対話形式でサポートを受けてください。