Workers の Vitest 連携は、テスト作成用のランタイムヘルパーを提供します。一部のヘルパーは cloudflare:workers モジュールから、ほかは cloudflare:test モジュールからエクスポートされます。どちらのモジュールも @cloudflare/vitest-plugin パッケージが提供しますが、Workers ランタイム内で実行するテストファイルからのみインポートできます。
-
env: import("cloudflare:workers").ProvidedEnv-
envオブジェクト を公開します。ES モジュール形式でエクスポートしたハンドラーに渡す第 2 引数として使います。Vitest 設定ファイル で定義した バインディング にアクセスできます。
import { env } from "cloudflare:workers"; it("uses binding", async () => { await env.KV_NAMESPACE.put("key", "value"); expect(await env.KV_NAMESPACE.get("key")).toBe("value"); });この値の型を設定するには、アンビエントモジュール型を使います。
declare module "cloudflare:workers" { interface ProvidedEnv { KV_NAMESPACE: KVNamespace; } // ...or if you have an existing `Env` type... interface ProvidedEnv extends Env {} }
-
-
exports: object-
mainWorker のエクスポートへアクセスできます。Worker のデフォルトエクスポートハンドラーに対する統合テストを書くときはexports.default.fetch()を使います。mainWorker はテストと同じアイソレート / コンテキストで動くため、グローバルなモックも適用されます。以前のSELFバインディングと違い、exportsは Assets を公開しません。アセットをテストするにはstartDevWorker()を使います。
import { exports } from "cloudflare:workers"; it("dispatches fetch event", async () => { const response = await exports.default.fetch("https://example.com"); expect(await response.text()).toMatchInlineSnapshot(...); });
-
-
createExecutionContext(): ExecutionContext- ES モジュール形式でエクスポートしたハンドラーの第 3 引数として使う
contextオブジェクト のインスタンスを作ります。
- ES モジュール形式でエクスポートしたハンドラーの第 3 引数として使う
-
waitOnExecutionContext(ctx:ExecutionContext): Promise<void>-
副作用に対するテストアサーションを実行する前に、
ctx.waitUntil()に渡したすべての Promise が決着するまで待ちます。createExecutionContext()が返したExecutionContextのインスタンスだけを受け付けます。
import { env } from "cloudflare:workers"; import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls fetch handler", async () => { const request = new Request("https://example.com"); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); expect(await response.text()).toMatchInlineSnapshot(...); });
-
-
createScheduledController(options?:FetcherScheduledOptions): ScheduledController-
モジュール形式の
scheduled()エクスポートハンドラーの第 1 引数として使うScheduledControllerのインスタンスを作ります。
import { env } from "cloudflare:workers"; import { createScheduledController, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls scheduled handler", async () => { const ctrl = createScheduledController({ scheduledTime: new Date(1000), cron: "30 * * * *" }); const ctx = createExecutionContext(); await worker.scheduled(ctrl, env, ctx); await waitOnExecutionContext(ctx); });
-
-
createMessageBatch(queueName:string, messages:ServiceBindingQueueMessage[]): MessageBatch- モジュール形式の
queue()エクスポートハンドラーの第 1 引数として使うMessageBatchのインスタンスを作ります。
- モジュール形式の
-
getQueueResult(batch:MessageBatch, ctx:ExecutionContext): Promise<FetcherQueueResult>-
MessageBatch内のメッセージの ack / retry 状態を取得し、ExecutionContext#waitUntil()したすべてのPromiseが決着するまで待ちます。createMessageBatch()が返したMessageBatchと、createExecutionContext()が返したExecutionContextだけを受け付けます。
import { env } from "cloudflare:workers"; import { createMessageBatch, createExecutionContext, getQueueResult } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; it("calls queue handler", async () => { const batch = createMessageBatch("my-queue", [ { id: "message-1", timestamp: new Date(1000), body: "body-1" } ]); const ctx = createExecutionContext(); await worker.queue(batch, env, ctx); const result = await getQueueResult(batch, ctx); expect(result.ackAll).toBe(false); expect(result.retryBatch).toMatchObject({ retry: false }); expect(result.explicitAcks).toStrictEqual(["message-1"]); expect(result.retryMessages).toStrictEqual([]); });
-
-
runInDurableObject<O extends DurableObject, R>(stub:DurableObjectStub, callback:(instance: O, state: DurableObjectState) => R | Promise<R>): Promise<R>-
指定した
stubに対応する Durable Object の内部で、指定したcallbackを実行します。
Durable Object の
fetch()ハンドラーを一時的にcallbackに置き換え、リクエストを送って結果を返します。Durable Object のメソッドの呼び出しやスパイ、永続データの投入 / 取得に使えます。mainWorker で定義した Durable Object を指すstubでのみ使えます。
export class Counter { constructor(readonly state: DurableObjectState) {} async fetch(request: Request): Promise<Response> { let count = (await this.state.storage.get<number>("count")) ?? 0; void this.state.storage.put("count", ++count); return new Response(count.toString()); } }import { env } from "cloudflare:workers"; import { runInDurableObject } from "cloudflare:test"; import { it, expect } from "vitest"; import { Counter } from "./index.ts"; it("increments count", async () => { const id = env.COUNTER.newUniqueId(); const stub = env.COUNTER.get(id); let response = await stub.fetch("https://example.com"); expect(await response.text()).toBe("1"); response = await runInDurableObject(stub, async (instance: Counter, state) => { expect(instance).toBeInstanceOf(Counter); expect(await state.storage.get<number>("count")).toBe(1); const request = new Request("https://example.com"); return instance.fetch(request); }); expect(await response.text()).toBe("2"); });
-
-
runDurableObjectAlarm(stub:DurableObjectStub): Promise<boolean>stubが指す Durable Object にアラームがスケジュールされていれば、ただちに実行して削除します。アラームが実行された場合はtrue、そうでなければfalseを返します。mainWorker で定義した Durable Object を指すstubでのみ使えます。
-
evictDurableObject(stub:DurableObjectStub, options?:DurableObjectEvictionOptions): Promise<void>-
stubが指す実行中の Durable Object を退避し、インスタンスを破棄してインメモリ状態をリセットします。既定では、休止可能な WebSocket は閉じられずにハイバネートされ、退避は実行中のリクエストが終わるまで最大 30 秒待ちます。
退避をまたいだ Durable Object の動き、たとえばストレージからの状態復元やハイバネートした WebSocket の再開をテストするときに使います。
stubが Durable Object の stub でない場合、対象の Durable Object が実行中でない場合、または名前空間で退避が禁止されている場合は reject します。mainWorker で定義した Durable Object を指すstubでのみ使えます。
import { env } from "cloudflare:workers"; import { evictDurableObject } from "cloudflare:test"; import { it, expect } from "vitest"; it("preserves stored data across eviction", async () => { const id = env.COUNTER.idFromName("evict-test"); const stub = env.COUNTER.get(id); // Each request increments and persists the count to storage expect(await (await stub.fetch("https://example.com")).text()).toBe("1"); expect(await (await stub.fetch("https://example.com")).text()).toBe("2"); // Evict the Durable Object. The in-memory instance is torn down, // but durable storage is preserved. await evictDurableObject(stub); // The next request reconstructs the instance and reads the persisted count expect(await (await stub.fetch("https://example.com")).text()).toBe("3"); }); -
DurableObjectEvictionOptionsインターフェイスで退避の動きを制御します。プロパティ 型 デフォルト 説明 webSockets"close" | "hibernate""hibernate"退避時に休止可能な WebSocket をどう扱うかを制御します。 "hibernate"では WebSocket をハイバネートし、退避後に再開できます。"close"では WebSocket を閉じます。
-
-
listDurableObjectIds(namespace:DurableObjectNamespace): Promise<DurableObjectId[]>-
namespace内で作成されたすべてのオブジェクトの ID を取得します。ファイル単位のストレージ分離に従うため、別のテストファイルで作成したオブジェクトは返りません。
import { env } from "cloudflare:workers"; import { listDurableObjectIds } from "cloudflare:test"; import { it, expect } from "vitest"; it("increments count", async () => { const id = env.COUNTER.newUniqueId(); const stub = env.COUNTER.get(id); const response = await stub.fetch("https://example.com"); expect(await response.text()).toBe("1"); const ids = await listDurableObjectIds(env.COUNTER); expect(ids.length).toBe(1); expect(ids[0].equals(id)).toBe(true); });
-
-
reset(): Promise<void>-
接続されているすべてのバインディングからデータを削除します。テストブロック間で状態をリセットするときに使います。
import { reset } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await reset(); });
-
-
abortAllDurableObjects(): Promise<void>-
すべての Durable Object インスタンスをリセットします。
reset()と違い、永続データは削除しません。実行中のすべての Durable Object インスタンスを強制的に破棄し、実行中のリクエストの完了を待たずにインメモリ状態を捨てます。
import { abortAllDurableObjects } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await abortAllDurableObjects(); });
-
-
evictAllDurableObjects(options?:DurableObjectEvictionOptions): Promise<void>-
退避可能な名前空間にある、実行中のすべての Durable Object を退避します。
abortAllDurableObjects()と違い、退避は強制ではなく、実行中の処理を待ってから行われます。既定では休止可能な WebSocket は閉じられずにハイバネートされ、退避は実行中のリクエストが終わるまで最大 30 秒待ちます。各インスタンスを破棄してインメモリ状態をリセットします。
実行中でない、またはアイドルの Durable Object はスキップします。退避が禁止されている名前空間も尊重します。
evictDurableObject()と同じDurableObjectEvictionOptionsを受け付けます。
import { evictAllDurableObjects } from "cloudflare:test"; import { afterEach } from "vitest"; afterEach(async () => { await evictAllDurableObjects(); });
-
-
applyD1Migrations(db:D1Database, migrations:D1Migration[], migrationTableName?:string): Promise<void>migrations配列に入っている未適用の D1 マイグレーション をデータベースdbに適用し、マイグレーション状態をmigrationsTableNameテーブルに記録します。migrationsTableNameの既定値はd1_migrationsです。migrations配列を取得するには、Node.js 内で@cloudflare/vitest-plugin/configパッケージのreadD1Migrations()関数を呼び出します。マイグレーションを使うプロジェクトの例は D1 レシピ ↗ を参照してください。
-
introspectWorkflowInstance(workflow: Workflow, instanceId: string): Promise<WorkflowInstanceIntrospector>-
特定の Workflow インスタンス用の introspector を作ります。テスト中にインスタンスの動きを 変更 し、結果を 待ち、状態を クリア します。ID が分かっている個々の Workflow インスタンスをテストするときの主な入り口です。
import { env } from "cloudflare:workers"; import { introspectWorkflowInstance } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION await using instance = await introspectWorkflowInstance(env.MY_WORKFLOW, "123456"); await instance.modify(async (m) => { await m.disableSleeps(); await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); }); // 2. EXECUTION await env.MY_WORKFLOW.create({ id: "123456" }); // 3. ASSERTION await expect(instance.waitForStatus("complete")).resolves.not.toThrow(); const output = await instance.getOutput(); expect(output).toEqual({ success: true }); // 4. DISPOSE: is implicit and automatic here. }); -
返される
WorkflowInstanceIntrospectorオブジェクトには、次のメソッドがあります。modify(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>: Workflow インスタンスの動きを変更します。waitForStepResult(step: { name: string; index?: number }): Promise<unknown>: 特定のステップが完了するまで待ち、結果を返します。同じ名前のステップが複数ある場合は、省略可能なindexプロパティ(1 始まり、既定は1)で対象を指定します。waitForStatus(status: InstanceStatus["status"]): Promise<void>: Workflow インスタンスが特定の ステータス(例: 'running'、'complete')になるまで待ちます。getOutput(): Promise<unknown>: 正常に完了した Workflow インスタンスの出力値を返します。getError(): Promise<{name: string, message: string}>: エラーになった Workflow インスタンスのエラー情報を返します。エラー情報の形は{ name: string; message: string }です。dispose(): Promise<void>: Workflow インスタンスを dispose します。テスト分離のために重要です。この関数を呼ばず、await usingも使わないと、分離ストレージが失敗し、インスタンスの状態が後続のテストに残ります。たとえば、あるテストで complete になったインスタンスは、次のテスト開始時にもすでに complete です。[Symbol.asyncDispose](): Promise<void>: 自動 dispose を提供します。await using文が呼び出してdispose()を実行します。
-
-
introspectWorkflow(workflow: Workflow): Promise<WorkflowIntrospector>-
インスタンス ID があらかじめ分からない Workflow 用の introspector を作ります。このあと作成されるすべてのインスタンス に適用する変更を定義できます。
import { env, exports } from "cloudflare:workers"; import { introspectWorkflow } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION await using introspector = await introspectWorkflow(env.MY_WORKFLOW); await introspector.modifyAll(async (m) => { await m.disableSleeps(); await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); }); // 2. EXECUTION await env.MY_WORKFLOW.create(); // 3. ASSERTION const instances = introspector.get(); for(const instance of instances) { await expect(instance.waitForStatus("complete")).resolves.not.toThrow(); const output = await instance.getOutput(); expect(output).toEqual({ success: true }); } // 4. DISPOSE: is implicit and automatic here. });Workflow インスタンスは、テスト内で直接作る必要はありません。introspector は初期化後に作られた すべての インスタンスを捕捉します。たとえば、Worker への 1 回の
fetchイベントで 1 つまたは複数 のインスタンス作成をトリガーできます。// This also works for the EXECUTION phase: await exports.default.fetch("https://example.com/trigger-workflows"); -
返される
WorkflowIntrospectorオブジェクトには、次のメソッドがあります。modifyAll(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>:introspectWorkflowの呼び出し後に作られたすべての Workflow インスタンスに変更を適用します。get(): Promise<WorkflowInstanceIntrospector[]>:introspectWorkflowの呼び出し後に作られたインスタンスの、すべてのWorkflowInstanceIntrospectorオブジェクトを返します。dispose(): Promise<void>: Workflow introspector を dispose します。作成済みインスタンスのWorkflowInstanceIntrospectorもすべて dispose されます。変更や捕捉したインスタンスがテスト間で漏れないようにするために重要です。このメソッドを呼んだあと、そのWorkflowIntrospectorを再利用しないでください。[Symbol.asyncDispose](): Promise<void>: 自動 dispose を提供します。await using文が呼び出してdispose()を実行します。
-
-
WorkflowInstanceModifier-
このオブジェクトは
modifyとmodifyAllのコールバックに渡され、Workflow インスタンスのステップ、イベント、スリープの動きをモックまたは変更します。disableSleeps(steps?: { name: string; index?: number }[]): スリープを無効にし、step.sleep()とstep.sleepUntil()をただちに解決します。stepsを省略すると、すべてのスリープを無効にします。disableRetryDelays(steps?: { name: string; index?: number }[]): リトライのバックオフ遅延を無効にし、失敗したstep.do()のリトライを待たずにただちに実行します。リトライ自体は行われます。なくなるのは試行間の遅延だけです。stepsを省略すると、すべてのリトライ遅延を無効にします。mockStepResult(step: { name: string; index?: number }, stepResult: unknown):step.do()の結果をモックし、ステップの実装を実行せずに指定した値をただちに返します。mockStepError(step: { name: string; index?: number }, error: Error, times?: number):step.do()にエラーを投げさせ、失敗をシミュレートします。timesは省略可能な数値で、ステップがエラーになる回数を指定します。timesを省略すると、毎回エラーになり、Workflow インスタンスは失敗します。forceStepTimeout(step: { name: string; index?: number }, times?: number):step.do()をただちにタイムアウトさせて失敗させます。timesは省略可能な数値で、ステップがタイムアウトする回数を指定します。timesを省略すると、毎回タイムアウトし、Workflow インスタンスは失敗します。mockEvent(event: { type: string; payload: unknown }): Workflow インスタンスにモックイベントを送り、step.waitForEvent()を指定したペイロードで解決します。typeはwaitForEventの type と一致する必要があります。forceEventTimeout(step: { name: string; index?: number }):step.waitForEvent()をただちにタイムアウトさせ、ステップを失敗させます。
import { env } from "cloudflare:workers"; import { introspectWorkflowInstance } from "cloudflare:test"; // This example showcases explicit disposal it("should apply all modifier functions", async () => { // 1. CONFIGURATION const instance = await introspectWorkflowInstance(env.COMPLEX_WORKFLOW, "123456"); try { // Modify instance behavior await instance.modify(async (m) => { // Disables all sleeps to make the test run instantly await m.disableSleeps(); // Disables retry backoff delays so retries execute without waiting await m.disableRetryDelays(); // Mocks the successful result of a data-fetching step await m.mockStepResult( { name: "get-order-details" }, { orderId: "abc-123", amount: 99.99 } ); // Mocks an incoming event to satisfy a `step.waitForEvent()` await m.mockEvent({ type: "user-approval", payload: { approved: true, approverId: "user-123" }, }); // Forces a step to fail once with a specific error to test retry logic await m.mockStepError( { name: "process-payment" }, new Error("Payment gateway timeout"), 1 // Fail only the first time ); // Forces a `step.do()` to time out immediately await m.forceStepTimeout({ name: "notify-shipping-partner" }); // Forces a `step.waitForEvent()` to time out await m.forceEventTimeout({ name: "wait-for-fraud-check" }); }); // 2. EXECUTION await env.COMPLEX_WORKFLOW.create({ id: "123456" }); // 3. ASSERTION expect(await instance.waitForStepResult({ name: "get-order-details" })).toEqual({ orderId: "abc-123", amount: 99.99, }); // Given the forced timeouts, the workflow will end in an errored state await expect(instance.waitForStatus("errored")).resolves.not.toThrow(); const error = await instance.getError(); expect(error.name).toEqual("Error"); expect(error.message).toContain("Execution timed out"); } catch { // 4. DISPOSE await instance.dispose(); } });ステップを対象にするときは
nameを使います。同じ名前のステップが複数ある場合は、省略可能なindexプロパティ(1 始まり、既定は1)で何回目かを指定します。
-