跳转到内容
搜索文档

测试 API

最后更新 查看 MarkdownAgent 设置

Workers Vitest 集成提供用于编写测试的运行时辅助函数。部分辅助函数从 cloudflare:workers 模块导出,其他从 cloudflare:test 模块导出。两个模块均由 @cloudflare/vitest-pool-workers 包提供,但只能从在 Workers 运行时中执行的测试文件导入。

cloudflare:workers 导出

  • env: import("cloudflare:workers").ProvidedEnv

    • 暴露 env 对象,用作传递给 ES 模块格式导出处理器的第二个参数。这提供对在 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 导出的访问。使用 exports.default.fetch() 针对 Worker 的默认导出处理器编写集成测试。main Worker 与测试在同一 isolate/context 中运行,因此任何全局 mock 也会应用于它。与之前的 SELF 绑定不同,exports 不暴露 Assets。要测试 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

    • 创建 context 对象 实例,用作传递给 ES 模块格式导出处理器的第三个参数。
  • 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

    • 创建 ScheduledController 实例,用作模块格式 scheduled() 导出处理器的第一个参数。


      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

    • 创建 MessageBatch 实例,用作模块格式 queue() 导出处理器的第一个参数。
  • getQueueResult(batch:MessageBatch, ctx:ExecutionContext): Promise<FetcherQueueResult>

    • 获取 MessageBatch 中消息的确认/重试状态,并等待所有 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 的 alarm(如果已调度)。如果 alarm 已运行则返回 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 当前未运行,或其命名空间禁止驱逐,则拒绝。注意,这只能用于指向 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 接口控制驱逐行为:

      Property Type Default Description
      webSockets "close" | "hibernate" "hibernate" 控制驱逐 Durable Object 时可休眠 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。在 Node.js 中从 @cloudflare/vitest-pool-workers/config 包调用 readD1Migrations() 函数以获取 migrations 数组。使用迁移的示例项目请参阅 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 实例,这对测试隔离至关重要。如果未调用此函数且未使用 await using,隔离存储将失败,实例状态会在后续测试中持续存在。例如,在一个测试中变为 completed 的实例在下一个测试开始时就已经是 completed 状态。
      • [Symbol.asyncDispose](): Promise<void>: 提供自动释放。由 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 发送单个 fetch 事件来触发创建一个或多个实例:

      // 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。来自已创建实例的所有 WorkflowInstanceIntrospector 也会被释放。这对防止修改和捕获的实例在测试之间泄漏至关重要。调用此方法后,不应再重用 WorkflowIntrospector
      • [Symbol.asyncDispose](): Promise<void>: 提供自动释放。由 await using 语句调用,会调用 dispose()
  • WorkflowInstanceModifier

    • 此对象提供给 modifymodifyAll 回调,用于 mock 或更改 Workflow 实例的步骤、事件和 sleep 行为。

      • disableSleeps(steps?: { name: string; index?: number }[]): 禁用 sleep,使 step.sleep()step.sleepUntil() 立即解析。如果省略 steps,则禁用所有 sleep。
      • disableRetryDelays(steps?: { name: string; index?: number }[]): 禁用重试退避延迟,使失败 step.do() 的重试尝试立即执行而不等待。重试仍会发生——仅移除它们之间的延迟。如果省略 steps,则禁用所有重试延迟。
      • mockStepResult(step: { name: string; index?: number }, stepResult: unknown): Mock 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 实例发送 mock 事件,使 step.waitForEvent() 以提供的 payload 解析。type 必须与 waitForEvent 类型匹配。
      • 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)来指定出现位置。

这篇文档对您有帮助吗?