このガイドでは、@cloudflare/vitest-plugin パッケージの始め方を説明します。@cloudflare/vitest-plugin を使ったより複雑なテスト例は、レシピ を参照してください。
まず、次を確認してください。
-
互換性日付 が
2022-10-31以降であること。 -
Worker が ES modules 形式を使っていること(そうでない場合は、ES modules 形式へ移行する ガイドを参照してください)。
-
プロジェクトに Vitest と
@cloudflare/vitest-pluginが開発用依存関係としてインストールされていることnpm i -D vitest@^4.1.0 @cloudflare/vitest-pluginyarn add -D vitest@^4.1.0 @cloudflare/vitest-pluginpnpm add -D vitest@^4.1.0 @cloudflare/vitest-pluginbun add -d vitest@^4.1.0 @cloudflare/vitest-plugin
vitest.config.ts で、cloudflareTest() プラグインを使い、Workers の Vitest 連携を設定します。
Wrangler 設定ファイル の Worker 設定を使うには、wrangler.configPath で指定します。
import { cloudflareTest } from "@cloudflare/vitest-plugin";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: "./wrangler.jsonc" },
}),
],
});miniflare キーで、追加の設定を定義したり、上書きしたりできます。こちらが、Wrangler 設定で指定した値より優先されます。
たとえば、次の設定は、テスト内でのみアクセス・変更される KV 名前空間 TEST_NAMESPACE を追加します。
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: "./wrangler.jsonc" },
miniflare: {
kvNamespaces: ["TEST_NAMESPACE"],
},
}),
],
});利用できる Miniflare オプションの一覧は、Miniflare の WorkersOptions API ドキュメント ↗ を参照してください。
利用できる設定オプションの一覧は、設定 を参照してください。
TypeScript を使っていない場合は、このセクションを飛ばせます。
まず wrangler types を実行してください。これにより、Cloudflare Workers ランタイム向けの型 と、Worker のバインディングに基づく Env 型が生成されます。
次に、テスト用フォルダーに tsconfig.json を追加し、cloudflare:test の型を定義するために types 配列へ "@cloudflare/vitest-plugin" を追加します。
Cloudflare Workers ランタイムの型を使えるように、wrangler types の出力も include 配列に追加してください。
test/tsconfig.json の例
{
"extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
"types": [
"@cloudflare/vitest-plugin/types", // provides `cloudflare:test` and `cloudflare:workers` types
],
},
"include": [
"./**/*.ts",
"../src/worker-configuration.d.ts", // output of `wrangler types`
],
}次のシンプルな Worker を例にします。/404 パスには 404 レスポンスを返し、それ以外のパスには "Hello World!" を返します。
export default {
async fetch(request, env, ctx) {
if (pathname === "/404") {
return new Response("Not found", { status: 404 });
}
return new Response("Hello World!");
},
};export default {
async fetch(request, env, ctx): Promise<Response> {
if (pathname === "/404") {
return new Response("Not found", { status: 404 });
}
return new Response("Hello World!");
},
} satisfies ExportedHandler<Env>;Worker をインポートすると、その fetch ハンドラーのユニットテストを書けます。
import { env } from "cloudflare:workers";
import {
createExecutionContext,
waitOnExecutionContext,
} from "cloudflare:test";
import { describe, it, expect } from "vitest";
// Import your worker so you can unit test it
import worker from "../src";
// For now, you'll need to do something like this to get a correctly-typed
// `Request` to pass to `worker.fetch()`.
const IncomingRequest = Request;
describe("Hello World worker", () => {
it("responds with Hello World!", async () => {
const request = new IncomingRequest("http://example.com/404");
// Create an empty context to pass to `worker.fetch()`
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});import { env } from "cloudflare:workers";
import {
createExecutionContext,
waitOnExecutionContext,
} from "cloudflare:test";
import { describe, it, expect } from "vitest";
// Import your worker so you can unit test it
import worker from "../src";
// For now, you'll need to do something like this to get a correctly-typed
// `Request` to pass to `worker.fetch()`.
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>;
describe("Hello World worker", () => {
it("responds with Hello World!", async () => {
const request = new IncomingRequest("http://example.com/404");
// Create an empty context to pass to `worker.fetch()`
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});cloudflare:workers が提供する exports オブジェクトを使い、統合テストを書けます。exports.default.fetch() は、main Worker で定義したデフォルトエクスポートのハンドラーを呼び出します。
import { exports } from "cloudflare:workers";
import { describe, it, expect } from "vitest";
describe("Hello World worker", () => {
it("responds with not found and proper status for /404", async () => {
const response = await exports.default.fetch("http://example.com/404");
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});import { exports } from "cloudflare:workers";
import { describe, it, expect } from "vitest";
describe("Hello World worker", () => {
it("responds with not found and proper status for /404", async () => {
const response = await exports.default.fetch("http://example.com/404");
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});exports.default.fetch() で統合テストを書く場合、Worker のコードはテストランナーと同じコンテキストで走ります。そのため、グローバルなモックで Worker を制御できます。一方で、Worker は Vite が提供する、微妙に異なるモジュール解決の挙動を使います。
通常は問題になりません。本番にできるだけ近い、新しい環境で Worker を走らせたい場合は、補助 Worker を使えます。補助 Worker を使った統合テストのセットアップは、この例 ↗ を参照してください。ただし、補助 Worker には把握しておくべき 制限 があります。
@cloudflare/vitest-pluginを使ったより複雑なテスト例は、レシピ を参照してください。- 設定 API リファレンス
- テスト API リファレンス