Test APIs·续

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-plugin version 1.0.0 and later.

  • 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.
    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\&lt;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**.

```ts
    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:

```js
    // 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.

```ts
    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.

#### Introspect Workflows declared in `exports`

Note

Requires `@cloudflare/vitest-plugin` 1.3.0 or above.

`introspectWorkflow()` and `introspectWorkflowInstance()` need a Workflow binding from `env`. They throw an error if you pass a Workflow from [`ctx.exports`](https://developers.cloudflare.com/workflows/build/workers-api/#call-a-workflow-through-ctxexports).

To introspect a Workflow declared in the [`exports`](https://developers.cloudflare.com/workers/wrangler/configuration/#workflow-exports) field of your Wrangler configuration, add a test-only binding to it in the `miniflare.workflows` option:

*vitest.config.tsts*

```ts
import { cloudflareTest } from "@cloudflare/vitest-plugin";
import { defineConfig } from "vitest/config";

export default defineConfig({
    plugins: [
        cloudflareTest({
            wrangler: {
                configPath: "./wrangler.jsonc",
            },
            miniflare: {
                // Test-only binding to the Workflow declared in `exports`
                workflows: {
                    MY_WORKFLOW: { name: "my-workflow", className: "MyWorkflow" },
                },
            },
        }),
    ],
});

The introspector also captures instances that your Worker creates through ctx.exports:

import { env, exports } from "cloudflare:workers";
import { introspectWorkflow } from "cloudflare:test";

it("introspects instances created through ctx.exports", async () => {
    await using introspector = await introspectWorkflow(env.MY_WORKFLOW);

// The Worker creates an instance through `ctx.exports.MyWorkflow`
    await exports.default.fetch("https://example.com/");

const instances = await introspector.get();
    expect(instances).toHaveLength(1);
    await instances[0].waitForStatus("complete");
});

Was this helpful?

YesNo

On this page

Test APIs·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/#page","headline":"Test APIs","description":"Runtime helpers for writing tests, exported from cloudflare:workers and cloudflare:test.","url":"https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/og.png?v=75594a5cce1aa029","dateModified":"2026-10-10","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Write tests against Workers using Vitest title: Write your first test image: https://developers.cloudflare.com/workers/testing/vitest-integration/write-your-first-test/og.png?v=4f540ef29b4b992d


Skip to content

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.