Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

テスト API

最終更新 Markdown で表示Agent セットアップ

Workers の Vitest 連携は、テスト作成用のランタイムヘルパーを提供します。一部のヘルパーは cloudflare:workers モジュールから、ほかは cloudflare:test モジュールからエクスポートされます。どちらのモジュールも @cloudflare/vitest-plugin パッケージが提供しますが、Workers ランタイム内で実行するテストファイルからのみインポートできます。

cloudflare: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

    • main Worker のエクスポートへアクセスできます。Worker のデフォルトエクスポートハンドラーに対する統合テストを書くときは exports.default.fetch() を使います。main Worker はテストと同じアイソレート / コンテキストで動くため、グローバルなモックも適用されます。以前の 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(...);
      });

cloudflare:test のエクスポート

イベント

  • createExecutionContext(): ExecutionContext

    • ES モジュール形式でエクスポートしたハンドラーの第 3 引数として使う context オブジェクト のインスタンスを作ります。
  • 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([]);
      });

Durable Objects

  • 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 のメソッドの呼び出しやスパイ、永続データの投入 / 取得に使えます。main Worker で定義した 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 を返します。main Worker で定義した 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 します。main Worker で定義した 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();
      });

D1

  • 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 レシピ を参照してください。

Workflows

  • 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

    • このオブジェクトは modifymodifyAll のコールバックに渡され、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() を指定したペイロードで解決します。typewaitForEvent の 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)で何回目かを指定します。

役に立ちましたか?