Test APIs
The Workers Vitest integration provides runtime helpers for writing tests in the cloudflare:test
module. The cloudflare:test
module is provided by the @cloudflare/vitest-pool-workers
package, but can only be imported from test files that execute in the Workers runtime.
-
env
: import("cloudflare:test").ProvidedEnv-
Exposes the
env
object for use as the second argument passed to ES modules format exported handlers. This provides access to bindings that you have defined in your Vitest configuration file.
JavaScript import { env } from "cloudflare:test";it("uses binding", async () => {await env.KV_NAMESPACE.put("key", "value");expect(await env.KV_NAMESPACE.get("key")).toBe("value");});To configure the type of this value, use an ambient module type:
TypeScript declare module "cloudflare:test" {interface ProvidedEnv {KV_NAMESPACE: KVNamespace;}// ...or if you have an existing `Env` type...interface ProvidedEnv extends Env {}}
-
-
SELF
: Fetcher-
Service binding to the default export defined in the
main
Worker. Use this to write integration tests against your Worker. Themain
Worker runs in the same isolate/context as tests so any global mocks will apply to it too.
JavaScript import { SELF } from "cloudflare:test";it("dispatches fetch event", async () => {const response = await SELF.fetch("https://example.com");expect(await response.text()).toMatchInlineSnapshot(...);});
-
-
fetchMock
: import("undici").MockAgent-
Declarative interface for mocking outbound
fetch()
requests. Deactivated by default and reset before running each test file. Refer toundici
'sMockAgent
documentation ↗ for more information. Note this only mocksfetch()
requests for the current test runner Worker. Auxiliary Workers should mockfetch()
es using the MiniflarefetchMock
/outboundService
options. Refer to Configuration for more information.
JavaScript import { fetchMock } from "cloudflare:test";import { beforeAll, afterEach, it, expect } from "vitest";beforeAll(() => {// Enable outbound request mocking...fetchMock.activate();// ...and throw errors if an outbound request isn't mockedfetchMock.disableNetConnect();});// Ensure we matched every mock we definedafterEach(() => fetchMock.assertNoPendingInterceptors());it("mocks requests", async () => {// Mock the first request to `https://example.com`fetchMock.get("https://example.com").intercept({ path: "/" }).reply(200, "body");const response = await fetch("https://example.com/");expect(await response.text()).toBe("body");});
-
-
createExecutionContext()
: ExecutionContext- Creates an instance of the
context
object for use as the third argument to ES modules format exported handlers.
- Creates an instance of the
-
waitOnExecutionContext(ctx:ExecutionContext)
: Promise<void>-
Use this to wait for all Promises passed to
ctx.waitUntil()
to settle, before running test assertions on any side effects. Only accepts instances ofExecutionContext
returned bycreateExecutionContext()
.
TypeScript import { env, 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-
Creates an instance of
ScheduledController
for use as the first argument to modules-formatscheduled()
exported handlers.
TypeScript import { env, 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- Creates an instance of
MessageBatch
for use as the first argument to modules-formatqueue()
exported handlers.
- Creates an instance of
-
getQueueResult(batch:MessageBatch, ctx:ExecutionContext)
: Promise<FetcherQueueResult>-
Gets the acknowledged/retry state of messages in the
MessageBatch
, and waits for allExecutionContext#waitUntil()
edPromise
s to settle. Only accepts instances ofMessageBatch
returned bycreateMessageBatch()
, and instances ofExecutionContext
returned bycreateExecutionContext()
.
TypeScript import { env, 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>-
Runs the provided
callback
inside the Durable Object that corresponds to the providedstub
.
This temporarily replaces your Durable Object's
fetch()
handler withcallback
, then sends a request to it, returning the result. This can be used to call/spy-on Durable Object methods or seed/get persisted data. Note this can only be used withstub
s pointing to Durable Objects defined in themain
Worker.
TypeScript 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());}}TypeScript import { env, 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>- Immediately runs and removes the Durable Object pointed to by
stub
's alarm if one is scheduled. Returnstrue
if an alarm ran, andfalse
otherwise. Note this can only be used withstub
s pointing to Durable Objects defined in themain
Worker.
- Immediately runs and removes the Durable Object pointed to by
-
listDurableObjectIds(namespace:DurableObjectNamespace)
: Promise<DurableObjectId[]>-
Gets the IDs of all objects that have been created in the
namespace
. RespectsisolatedStorage
if enabled, meaning objects created in a different test will not be returned.
TypeScript import { env, 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);});
-
-
applyD1Migrations(db:D1Database, migrations:D1Migration[], migrationTableName?:string)
: Promise<void>- Applies all un-applied D1 migrations stored in the
migrations
array to databasedb
, recording migrations state in themigrationsTableName
table.migrationsTableName
defaults tod1_migrations
. Call thereadD1Migrations()
function from the@cloudflare/vitest-pool-workers/config
package inside Node.js to get themigrations
array. Refer to the D1 recipe ↗ for an example project using migrations.
- Applies all un-applied D1 migrations stored in the
-
introspectWorkflowInstance(workflow: Workflow, instanceId: string)
: Promise<WorkflowInstanceIntrospector>-
Creates an introspector for a specific Workflow instance, used to modify its behavior, await outcomes, and clear its state during tests. This is the primary entry point for testing individual Workflow instances with a known ID.
TypeScript import { env, introspectWorkflowInstance } from "cloudflare:test";it("should disable all sleeps, mock an event and complete", async () => {// 1. CONFIGURATIONawait 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. EXECUTIONawait env.MY_WORKFLOW.create({ id: "123456" });// 3. ASSERTIONawait expect(instance.waitForStatus("complete")).resolves.not.toThrow();// 4. DISPOSE: is implicit and automatic here.}); -
The returned
WorkflowInstanceIntrospector
object has the following methods:modify(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>
: Applies modifications to the Workflow instance's behavior.waitForStepResult(step: { name: string; index?: number }): Promise<unknown>
: Waits for a specific step to complete and returns a result. If multiple steps share the same name, use the optionalindex
property (1-based, defaults to1
) to target a specific occurrence.waitForStatus(status: InstanceStatus["status"]): Promise<void>
: Waits for the Workflow instance to reach a specific status (e.g., 'running', 'complete').dispose(): Promise<void>
: Disposes the Workflow instance, which is crucial for test isolation. If this function isn't called andawait using
is not used, isolated storage will fail and the instance's state will persist across subsequent tests. For example, an instance that becomes completed in one test will already be completed at the start of the next.[Symbol.asyncDispose](): Provides automatic dispose. It's invoked by the
await usingstatement, which calls
dispose()`.
-
-
introspectWorkflow(workflow: Workflow)
: Promise<WorkflowIntrospector>-
Creates an introspector for a Workflow where instance IDs are unknown beforehand. This allows for defining modifications that will apply to all subsequently created instances.
TypeScript import { env, introspectWorkflow, SELF } from "cloudflare:test";it("should disable all sleeps, mock an event and complete", async () => {// 1. CONFIGURATIONawait 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. EXECUTIONawait env.MY_WORKFLOW.create();// 3. ASSERTIONconst instances = introspector.get();for(const instance of instances) {await expect(instance.waitForStatus("complete")).resolves.not.toThrow();}// 4. DISPOSE: is implicit and automatic here.});The workflow instance doesn't have to be created directly inside the test. The introspector will capture all instances created after it is initialized. For example, you could trigger the creation of one or multiple instances via a single
fetch
event to your Worker:JavaScript // This also works for the EXECUTION phase:await SELF.fetch("https://example.com/trigger-workflows"); -
The returned
WorkflowIntrospector
object has the following methods:modifyAll(fn: (m: WorkflowInstanceModifier) => Promise<void>): Promise<void>
: Applies modifications to all Workflow instances created after callingintrospectWorkflow
.get(): Promise<WorkflowInstanceIntrospector[]>
: Returns allWorkflowInstanceIntrospector
objects from instances created afterintrospectWorkflow
was called.dispose(): Promise<void>
: Disposes the Workflow introspector. AllWorkflowInstanceIntrospector
from created instances will also be disposed. This is crucial to prevent modifications and captured instances from leaking between tests. After calling this method, theWorkflowIntrospector
should not be reused.[Symbol.asyncDispose](): Promise<void>
: Provides automatic dispose. It's invoked by theawait using
statement, which callsdispose()
.
-
-
WorkflowInstanceModifier
-
This object is provided to the
modify
andmodifyAll
callbacks to mock or alter the behavior of a Workflow instance's steps, events, and sleeps.disableSleeps(steps?: { name: string; index?: number }[])
: Disables sleeps, causingstep.sleep()
andstep.sleepUntil()
to resolve immediately. Ifsteps
is omitted, all sleeps are disabled.mockStepResult(step: { name: string; index?: number }, stepResult: unknown)
: Mocks the result of astep.do()
, causing it to return the specified value instantly without executing the step's implementation.mockStepError(step: { name: string; index?: number }, error: Error, times?: number)
: Forces astep.do()
to throw an error, simulating a failure.times
is an optional number that sets how many times the step should error. Iftimes
is omitted, the step will error on every attempt, making the Workflow instance fail.forceStepTimeout(step: { name: string; index?: number }, times?: number)
: Forces astep.do()
to fail by timing out immediately.times
is an optional number that sets how many times the step should timeout. Iftimes
is omitted, the step will timeout on every attempt, making the Workflow instance fail.mockEvent(event: { type: string; payload: unknown })
: Sends a mock event to the Workflow instance, causing astep.waitForEvent()
to resolve with the provided payload.type
must match thewaitForEvent
type.forceEventTimeout(step: { name: string; index?: number })
: Forces astep.waitForEvent()
to time out instantly, causing the step to fail.
TypeScript import { env, introspectWorkflowInstance } from "cloudflare:test";// This example showcases explicit disposalit("should apply all modifier functions", async () => {// 1. CONFIGURATIONconst instance = await introspectWorkflowInstance(env.COMPLEX_WORKFLOW, "123456");try {// Modify instance behaviorawait instance.modify(async (m) => {// Disables all sleeps to make the test run instantlyawait m.disableSleeps();// Mocks the successful result of a data-fetching stepawait 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 logicawait m.mockStepError({ name: "process-payment" },new Error("Payment gateway timeout"),1 // Fail only the first time);// Forces a `step.do()` to time out immediatelyawait m.forceStepTimeout({ name: "notify-shipping-partner" });// Forces a `step.waitForEvent()` to time outawait m.forceEventTimeout({ name: "wait-for-fraud-check" });});// 2. EXECUTIONawait env.COMPLEX_WORKFLOW.create({ id: "123456" });// 3. ASSERTIONexpect(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 stateawait expect(instance.waitForStatus("errored")).resolves.not.toThrow();} catch {// 4. DISPOSEawait instance.dispose();}});When targeting a step, use its
name
. If multiple steps share the same name, use the optionalindex
property (1-based, defaults to1
) to specify the occurrence.
-
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-