Skip to content

Commit 098c475

Browse files
committed
Add Workflows test support APIs
1 parent 72f0335 commit 098c475

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

src/content/docs/workers/testing/vitest-integration/test-apis.mdx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ description: Runtime helpers for writing tests, exported from the `cloudflare:te
88

99
---
1010

11+
import { InlineBadge } from '~/components';
12+
1113
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.
1214

1315
## `cloudflare:test` module definition
@@ -258,3 +260,94 @@ The Workers Vitest integration provides runtime helpers for writing tests in the
258260

259261
* Applies all un-applied [D1 migrations](/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()`](/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.
260262

263+
264+
### Workflows <InlineBadge preset="beta" />
265+
266+
267+
268+
:::caution[Workflows currently require `isolatedStorage` to be disabled]
269+
270+
To test projects that use Workflows, you **must** set `isolatedStorage` to `false` in your `vitest.config.ts` file.
271+
272+
:::
273+
274+
* `introspectWorkflowInstance(workflow: Workflow, instanceId: string)`: Promise\<WorkflowInstanceIntrospector>
275+
* Creates an **introspector** for a specific Workflow instance, used to **modify** its behavior, **await** outcomes, and **clean up** its state during tests. This is the primary entry point for testing individual Workflow instances with a known ID.
276+
<br/>
277+
278+
```ts
279+
import { env, introspectWorkflowInstance } from "cloudflare:test";
280+
281+
it("should disable all sleeps, mock an event and complete", async () => {
282+
// 1. CONFIGURATION
283+
const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, "123456");
284+
await instance.modify(async (m) => {
285+
await m.disableSleeps();
286+
await m.mockEvent({ type: "user-approval" }, { approval: true });
287+
});
288+
289+
// 2. EXECUTION
290+
await env.MY_WORKFLOW.create({ id: "123456" });
291+
292+
// 3. ASSERTION
293+
await instance.waitForStatus("complete");
294+
295+
// 4. CLEANUP
296+
await instance.cleanUp();
297+
});
298+
```
299+
* The returned `WorkflowInstanceIntrospector` object has the following methods:
300+
* `modify(fn: (m: WorkflowInstanceModifier) => Promise<void>)`: Applies modifications to the Workflow instance's behavior.
301+
* `waitForStepResult(step: { name: string; index?: number })`: Waits for a specific step to complete and return a result. If multiple steps share the same name, use the optional `index` property (1-based, defaults to `1`) to target a specific occurrence.
302+
* `waitForStatus(status: InstanceStatus["status"])`: Waits for the Workflow instance to reach a specific [status](/workflows/build/workers-api/#instancestatus) (e.g., 'running', 'complete').
303+
* `cleanUp()`: Cleans up the Workflow instance's state. It is crucial to call this after each test to ensure isolation.
304+
305+
* `introspectWorkflow(workflow: Workflow)`: Promise\<WorkflowIntrospector>
306+
* 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**.
307+
<br/>
308+
309+
```ts
310+
import { env, introspectWorkflow, SELF } from "cloudflare:test";
311+
312+
it("should disable all sleeps, mock an event and complete", async () => {
313+
// 1. CONFIGURATION
314+
const introspector = await introspectWorkflow(env.MY_WORKFLOW);
315+
await introspector.modifyAll(async (m) => {
316+
await m.disableSleeps();
317+
await m.mockEvent({ type: "user-approval" }, { approval: true });
318+
});
319+
320+
// 2. EXECUTION
321+
await env.MY_WORKFLOW.create();
322+
323+
// 3. ASSERTION & CLEANUP
324+
const instances = introspector.get();
325+
for(const instance of instances) {
326+
await instance.waitForStatus("complete");
327+
await instance.cleanUp();
328+
}
329+
330+
introspector.cleanUp();
331+
});
332+
```
333+
The workflow instance doesn't have to be created inside the test. The workflow instance creation doesn't have to be direct. 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:
334+
```js
335+
// This also works for the EXECUTION phase:
336+
await SELF.fetch("https://example.com/trigger-workflows");
337+
```
338+
339+
* The returned `WorkflowIntrospector` object has the following methods:
340+
* `modifyAll(fn: (m: WorkflowInstanceModifier) => Promise<void>)`: Applies modifications to all Workflow instances created after calling `introspectWorkflow`.
341+
* `get()`: Returns all `WorkflowInstanceIntrospector` objects from instances created after `introspectWorkflow` was called.
342+
* `cleanUp()`: Cleans up and stops the introspection process for the Workflow. This is crucial to prevent modifications and captured instances from leaking between tests.
343+
344+
* `WorkflowInstanceModifier`
345+
* 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.
346+
* `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.
347+
* `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.
348+
* `mockStepError(step: { name: string; index?: number }, error: Error, times?: number)`: Forces a `step.do()` to throw an error, simulating a failure. Use the `times` parameter to test retry logic.
349+
* `forceStepTimeout(step: { name: string; index?: number }, times?: number)`: Forces a `step.do()` to fail by timing out immediately.
350+
* `mockEvent(event: { type: string; payload: unknown })`: Sends a mock event to the Workflow instance, causing a `step.waitForEvent()` to resolve with the provided payload.
351+
* `forceEventTimeout(step: { name: string; index?: number })`: Forces a `step.waitForEvent()` to time out instantly, causing the step to fail.
352+
353+
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.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Test Workflows
3+
pcx_content_type: navigation
4+
external_link: /workers/testing/vitest-integration/test-apis/#workflows
5+
sidebar:
6+
order: 11
7+
badge:
8+
text: Beta
9+
---

0 commit comments

Comments
 (0)