---
title: Test APIs
description: Runtime helpers for writing tests, exported from `cloudflare:workers` and `cloudflare:test`.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Test APIs

The Workers Vitest integration provides runtime helpers for writing tests. Some helpers are exported from the `cloudflare:workers` module, and others from the `cloudflare:test` module. Both modules are provided by the `@cloudflare/vitest-pool-workers` package, but can only be imported from test files that execute in the Workers runtime.

## `cloudflare:workers` exports

* `env`: import("cloudflare:workers").ProvidedEnv  
   * Exposes the [env object](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters) for use as the second argument passed to ES modules format exported handlers. This provides access to [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) that you have defined in your [Vitest configuration file](https://developers.cloudflare.com/workers/testing/vitest-integration/configuration/).  
         
   JavaScript  
   ```  
   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");  
   });  
   ```  
   To configure the type of this value, use an ambient module type:  
   TypeScript  
   ```  
   declare module "cloudflare:workers" {  
     interface ProvidedEnv {  
       KV_NAMESPACE: KVNamespace;  
     }  
     // ...or if you have an existing `Env` type...  
     interface ProvidedEnv extends Env {}  
   }  
   ```
* `exports`: object  
   * Provides access to the exports of the `main` Worker. Use `exports.default.fetch()` to write integration tests against your Worker's default export handler. The `main` Worker runs in the same isolate/context as tests so any global mocks will apply to it too. Unlike the previous `SELF` binding, `exports` does not expose Assets. To test assets, use [startDevWorker()](https://developers.cloudflare.com/workers/testing/unstable%5Fstartworker/).  
         
   JavaScript  
   ```  
   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` exports

### Events

* `createExecutionContext()`: ExecutionContext  
   * Creates an instance of the [context object](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters) for use as the third argument to ES modules format exported handlers.
* `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 of `ExecutionContext` returned by `createExecutionContext()`.  
         
   TypeScript  
   ```  
   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  
   * Creates an instance of `ScheduledController` for use as the first argument to modules-format [scheduled()](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/) exported handlers.  
         
   TypeScript  
   ```  
   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  
   * Creates an instance of `MessageBatch` for use as the first argument to modules-format [queue()](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer) exported handlers.
* `getQueueResult(batch:MessageBatch, ctx:ExecutionContext)`: Promise<FetcherQueueResult>  
   * Gets the acknowledged/retry state of messages in the `MessageBatch`, and waits for all `ExecutionContext#waitUntil()`ed `Promise`s to settle. Only accepts instances of `MessageBatch` returned by `createMessageBatch()`, and instances of `ExecutionContext` returned by `createExecutionContext()`.  
         
   TypeScript  
   ```  
   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>  
   * Runs the provided `callback` inside the Durable Object that corresponds to the provided `stub`.  
         
   This temporarily replaces your Durable Object's `fetch()` handler with `callback`, 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 with `stub`s pointing to Durable Objects defined in the `main` 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 } 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>  
   * Immediately runs and removes the Durable Object pointed to by `stub`'s alarm if one is scheduled. Returns `true` if an alarm ran, and `false` otherwise. Note this can only be used with `stub`s pointing to Durable Objects defined in the `main` Worker.
* `listDurableObjectIds(namespace:DurableObjectNamespace)`: Promise<DurableObjectId\[\]>  
   * Gets the IDs of all objects that have been created in the `namespace`. Respects per-file storage isolation, meaning objects created in a different test file will not be returned.  
         
   TypeScript  
   ```  
   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);  
   });  
   ```

### D1

* `applyD1Migrations(db:D1Database, migrations:D1Migration[], migrationTableName?:string)`: Promise<void>  
   * Applies all un-applied [D1 migrations](https://developers.cloudflare.com/d1/reference/migrations/) stored in the `migrations` array to database `db`, recording migrations state in the `migrationsTableName` table. `migrationsTableName` defaults to `d1_migrations`. Call the [readD1Migrations()](https://developers.cloudflare.com/workers/testing/vitest-integration/configuration/#readd1migrationsmigrationspath) function from the `@cloudflare/vitest-pool-workers/config` package inside Node.js to get the `migrations` array. Refer to the [D1 recipe ↗](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples/d1) for an example project using migrations.

### Workflows

Workflows with storage isolation

To ensure proper test isolation in Workflows with per-file storage isolation, introspectors should be disposed at the end of each test. This is accomplished by either:

* Using an `await using` statement on the introspector.
* Explicitly calling the introspector `dispose()` method.

Version

Available in `@cloudflare/vitest-pool-workers` version **0.9.0**!

* `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 } 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.  
   });  
   ```  
   * 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 optional `index` property (1-based, defaults to `1`) to target a specific occurrence.  
         * `waitForStatus(status: InstanceStatus["status"]): Promise<void>`: Waits for the Workflow instance to reach a specific [status](https://developers.cloudflare.com/workflows/build/workers-api/#instancestatus) (e.g., 'running', 'complete').  
         * `getOutput(): Promise<unknown>`: Returns the output value of the successful completed Workflow instance.  
         * `getError(): Promise<{name: string, message: string}>`: Returns the error information of the errored Workflow instance. The error information follows the form `{ name: string; message: string }`.  
         * `dispose(): Promise<void>`: Disposes the Workflow instance, which is crucial for test isolation. If this function isn't called and `await 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](): Promise<void>`: Provides automatic dispose. It's invoked by the `await using` statement, 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, 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.  
   });  
   ```  
   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 exports.default.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 calling `introspectWorkflow`.  
         * `get(): Promise<WorkflowInstanceIntrospector[]>`: Returns all `WorkflowInstanceIntrospector` objects from instances created after `introspectWorkflow` was called.  
         * `dispose(): Promise<void>`: Disposes the Workflow introspector. All `WorkflowInstanceIntrospector` from created instances will also be disposed. This is crucial to prevent modifications and captured instances from leaking between tests. After calling this method, the `WorkflowIntrospector` should not be reused.  
         * `[Symbol.asyncDispose](): Promise<void>`: Provides automatic dispose. It's invoked by the `await using` statement, which calls `dispose()`.
* `WorkflowInstanceModifier`  
   * This object is provided to the `modify` and `modifyAll` callbacks to mock or alter the behavior of a Workflow instance's steps, events, and sleeps.  
         * `disableSleeps(steps?: { name: string; index?: number }[])`: Disables sleeps, causing `step.sleep()` and `step.sleepUntil()` to resolve immediately. If `steps` is omitted, all sleeps are disabled.  
         * `disableRetryDelays(steps?: { name: string; index?: number }[])`: Disables retry backoff delays, causing retry attempts of a failing `step.do()` to execute immediately without waiting. The retries still happen — only the delay between them is removed. If `steps` is omitted, all retry delays are disabled.  
         * `mockStepResult(step: { name: string; index?: number }, stepResult: unknown)`: Mocks the result of a `step.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 a `step.do()` to throw an error, simulating a failure. `times` is an optional number that sets how many times the step should error. If `times` is omitted, the step will error on every attempt, making the Workflow instance fail.  
         * `forceStepTimeout(step: { name: string; index?: number }, times?: number)`: Forces a `step.do()` to fail by timing out immediately. `times` is an optional number that sets how many times the step should timeout. If `times` 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 a `step.waitForEvent()` to resolve with the provided payload. `type` must match the `waitForEvent` type.  
         * `forceEventTimeout(step: { name: string; index?: number })`: Forces a `step.waitForEvent()` to time out instantly, causing the step to fail.  
         
   TypeScript  
   ```  
   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();  
     }  
   });  
   ```  
   When targeting a step, use its `name`. If multiple steps share the same name, use the optional `index` property (1-based, defaults to `1`) to specify the occurrence.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/testing/","name":"Testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/testing/vitest-integration/","name":"Vitest integration"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/testing/vitest-integration/test-apis/","name":"Test APIs"}}]}
```
