lynx   »   [go: up one dir, main page]

Skip to content
Cloudflare Docs

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.

cloudflare:test module definition

  • 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. The main 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 to undici's MockAgent documentation for more information. Note this only mocks fetch() requests for the current test runner Worker. Auxiliary Workers should mock fetch()es using the Miniflare fetchMock/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 mocked
      fetchMock.disableNetConnect();
      });
      // Ensure we matched every mock we defined
      afterEach(() => 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");
      });

Events

  • createExecutionContext(): ExecutionContext

    • Creates an instance of the context object 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, 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() 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-format queue() 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 Promises to settle. Only accepts instances of MessageBatch returned by createMessageBatch(), and instances of ExecutionContext returned by createExecutionContext().


      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([]);
      });

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 stubs 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, 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 stubs 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 isolatedStorage 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);
      });

D1

  • applyD1Migrations(db:D1Database, migrations:D1Migration[], migrationTableName?:string): Promise<void>

    • Applies all un-applied D1 migrations stored in the migrations array to database db, recording migrations state in the migrationsTableName table. migrationsTableName defaults to d1_migrations. Call the readD1Migrations() function from the @cloudflare/vitest-pool-workers/config package inside Node.js to get the migrations array. Refer to the D1 recipe for an example project using migrations.

Workflows

  • 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. 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();
      // 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 (e.g., 'running', 'complete').
      • 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](): Provides automatic dispose. It's invoked by the await usingstatement, which callsdispose()`.
  • 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. 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();
      }
      // 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 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.
      • 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, 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();
      // 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();
      } 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.

Лучший частный хостинг