Workers Vitest 集成提供用于编写测试的运行时辅助函数。部分辅助函数从 cloudflare:workers 模块导出,其他从 cloudflare:test 模块导出。两个模块均由 @cloudflare/vitest-pool-workers 包提供,但只能从在 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-
提供对
mainWorker 导出的访问。使用exports.default.fetch()针对 Worker 的默认导出处理器编写集成测试。mainWorker 与测试在同一 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(...); });
-
-
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([]); });
-
-
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 的 alarm(如果已调度)。如果 alarm 已运行则返回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 当前未运行,或其命名空间禁止驱逐,则拒绝。注意,这只能用于指向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接口控制驱逐行为: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(); });
-
-
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 示例 ↗。
- 将
-
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-
此对象提供给
modify和modifyAll回调,用于 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): Mockstep.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)来指定出现位置。
-