new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Cloudflare Workers · all subjects

testing

300 notes in this subject, read out of this brain and free to use. This is page 5 of 5.

Vitest integration supports testing Workers bindings for AI, Vectorize, and Images

Test recipes are available for mocking Workers AI and Vectorize bindings in unit tests, as well as for testing the Images binding.

Vitest integration supports testing WebAssembly and RPC

The integration provides examples for importing and testing WebAssembly modules, as well as testing JSRPC with entrypoints and Durable Objects.

Vitest integration supports accessing Worker exports via ctx.exports

Tests can use ctx.exports to access Worker exports in the testing context.

Override Vitest configuration with miniflare key

The miniflare key in cloudflareTest() configuration takes precedence over values set via your Wrangler config. This allows you to add test-specific bindings and configuration. For example, you can add a KV namespace only used in tests using kvNamespaces: ["TEST_NAMESPACE"].

Configure Vitest with cloudflareTest plugin

In your vitest.config.ts file, use the cloudflareTest() plugin from @cloudflare/vitest-pool-workers to configure the Workers Vitest integration. The plugin accepts configuration including wrangler.configPath to use your Wrangler config file and a miniflare key to override or add configuration.

Worker module format requirement for Vitest testing

Your Worker must use the ES modules format to use @cloudflare/vitest-pool-workers. If your Worker does not use ES modules format, refer to the migrate to the ES modules format guide.

Worker compatibility date requirement for Vitest testing

Your compatibility date must be set to 2022-10-31 or later to use @cloudflare/vitest-pool-workers.

TypeScript types for Vitest testing

When using TypeScript, run wrangler types to generate types for the Cloudflare Workers runtime and an Env type based on your Worker's bindings. Add a tsconfig.json in your tests folder with "@cloudflare/vitest-pool-workers/types" in the types array to define types for cloudflare:test. Include the output of wrangler types in the include array so runtime types are available.

Example test/tsconfig.json for Vitest

Example test/tsconfig.json configuration: ```jsonc { "extends": "../tsconfig.json", "compilerOptions": { "moduleResolution": "bundler", "types": [ "@cloudflare/vitest-pool-workers/types" ] }, "include": [ "./**/*.ts", "../src/worker-configuration.d.ts" ] } ```

Unit testing a Worker with Vitest

To write unit tests for a Worker, import the worker and the cloudflare:test module which provides createExecutionContext() and waitOnExecutionContext(). Create a request, call worker.fetch() with the request, environment, and execution context, then await waitOnExecutionContext() before asserting on the response. Example unit test: ```ts import { env } from "cloudflare:workers"; import { createExecutionContext, waitOnExecutionContext, } from "cloudflare:test"; import { describe, it, expect } from "vitest"; import worker from "../src"; const IncomingRequest = Request<unknown, IncomingRequestCfProperties>; describe("Hello World worker", () => { it("responds with Hello World!", async () => { const request = new IncomingRequest("http://example.com/404"); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); await waitOnExecutionContext(ctx); expect(response.status).toBe(404); expect(await response.text()).toBe("Not found"); }); }); ```

Integration testing a Worker with Vitest

For integration tests, use the exports object from cloudflare:workers to call exports.default.fetch() which invokes the default export handler. The Worker code runs in the same context as the test runner, allowing use of global mocks. Example integration test: ```ts import { exports } from "cloudflare:workers"; import { describe, it, expect } from "vitest"; describe("Hello World worker", () => { it("responds with not found and proper status for /404", async () => { const response = await exports.default.fetch("http://example.com/404"); expect(response.status).toBe(404); expect(await response.text()).toBe("Not found"); }); }); ```

Integration test behavior and module resolution in Vitest

When using exports.default.fetch() for integration tests, your Worker code runs in the same context as the test runner. This means you can use global mocks to control your Worker, but your Worker uses the subtly different module resolution behavior provided by Vite. To run your Worker in a fresh environment as close to production as possible, you can use an auxiliary Worker, though this comes with limitations.

Vitest @cloudflare/vitest-pool-workers package version requirement

The @cloudflare/vitest-pool-workers package requires Vitest 4.1 or later.

ProvidedEnv type configuration example

Example of configuring the env type with ambient module declaration: declare module 'cloudflare:workers' { interface ProvidedEnv { KV_NAMESPACE: KVNamespace; } // or if you have an existing Env type: interface ProvidedEnv extends Env {} }

WorkflowInstanceModifier methods

The `WorkflowInstanceModifier` object provided to `modify()` and `modifyAll()` callbacks provides the following methods: `disableSleeps(steps?)` - disables all sleeps or specific ones; `disableRetryDelays(steps?)` - disables retry backoff delays; `mockStepResult(step, stepResult)` - mocks the result of a step.do(); `mockStepError(step, error, times?)` - forces a step to throw an error; `forceStepTimeout(step, times?)` - forces a step to timeout; `mockEvent(event)` - sends a mock event to satisfy step.waitForEvent(); `forceEventTimeout(step)` - forces a step.waitForEvent() to timeout. When targeting a step, use its `name`. If multiple steps share the same name, use the optional `index` property (1-based, defaults to `1`).

env object from cloudflare:workers

The `env` object exported from the `cloudflare:workers` module provides access to bindings defined in the Vitest configuration file. It is used as the second argument to ES modules format exported handlers. The type can be configured using an ambient module declaration extending the `ProvidedEnv` interface.

exports object from cloudflare:workers

The `exports` object exported from the `cloudflare:workers` module provides access to the exports of the main Worker. Use `exports.default.fetch()` to write integration tests against a Worker's default export handler. The main Worker runs in the same isolate as tests so global mocks apply to it. Unlike the previous SELF binding, `exports` does not expose Assets.

createExecutionContext from cloudflare:test

The `createExecutionContext()` function exported from `cloudflare:test` creates an instance of the `ExecutionContext` object for use as the third argument to ES modules format exported handlers.

waitOnExecutionContext from cloudflare:test

The `waitOnExecutionContext(ctx: ExecutionContext)` function waits for all Promises passed to `ctx.waitUntil()` to settle before running test assertions on side effects. It only accepts instances of `ExecutionContext` returned by `createExecutionContext()`.

createScheduledController from cloudflare:test

The `createScheduledController(options?: FetcherScheduledOptions)` function creates an instance of `ScheduledController` for use as the first argument to modules-format `scheduled()` exported handlers. The options object accepts `scheduledTime` as a Date and `cron` as a string.

createMessageBatch from cloudflare:test

The `createMessageBatch(queueName: string, messages: ServiceBindingQueueMessage[])` function creates an instance of `MessageBatch` for use as the first argument to modules-format `queue()` exported handlers.

getQueueResult from cloudflare:test

The `getQueueResult(batch: MessageBatch, ctx: ExecutionContext)` function gets the acknowledged/retry state of messages in a `MessageBatch` and waits for all `ExecutionContext#waitUntil()`ed Promises to settle. It only accepts instances of `MessageBatch` returned by `createMessageBatch()` and instances of `ExecutionContext` returned by `createExecutionContext()`.

runInDurableObject from cloudflare:test

The `runInDurableObject<O extends DurableObject, R>(stub: DurableObjectStub, callback: (instance: O, state: DurableObjectState) => R | Promise<R>)` function runs a callback inside the Durable Object that corresponds to the provided stub. It temporarily replaces the Durable Object's `fetch()` handler with the 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. It only works with stubs pointing to Durable Objects defined in the main Worker.

runDurableObjectAlarm from cloudflare:test

The `runDurableObjectAlarm(stub: DurableObjectStub)` function immediately runs and removes the Durable Object alarm if one is scheduled. It returns `true` if an alarm ran and `false` otherwise. It only works with stubs pointing to Durable Objects defined in the main Worker.

evictDurableObject from cloudflare:test

The `evictDurableObject(stub: DurableObjectStub, options?: DurableObjectEvictionOptions)` function evicts a currently-running Durable Object, tearing down its instance to reset in-memory state. By default, hibernatable WebSockets are hibernated rather than closed, and eviction waits up to 30 seconds for in-flight requests to drain. The `webSockets` property in `DurableObjectEvictionOptions` can be set to `'close' | 'hibernate'` (defaults to `'hibernate'`) to control WebSocket behavior.

listDurableObjectIds from cloudflare:test

The `listDurableObjectIds(namespace: DurableObjectNamespace)` function returns the IDs of all objects that have been created in a namespace as an array of `DurableObjectId`. It respects per-file storage isolation, so objects created in a different test file will not be returned.

reset from cloudflare:test

The `reset()` function deletes all data from all attached bindings. It is useful for resetting state between test blocks.

abortAllDurableObjects from cloudflare:test

The `abortAllDurableObjects()` function resets all Durable Object instances. Unlike `reset()`, it does not delete persisted data. It forcibly tears down all running Durable Object instances, discarding in-memory state without waiting for in-flight requests to drain.

evictAllDurableObjects from cloudflare:test

The `evictAllDurableObjects(options?: DurableObjectEvictionOptions)` function evicts all currently-running Durable Objects in evictable namespaces. Unlike `abortAllDurableObjects()`, eviction is graceful: hibernatable WebSockets are hibernated rather than closed by default, and eviction waits up to 30 seconds for in-flight requests to drain. Non-running or idle Durable Objects are skipped, and namespaces with eviction prevented are respected.

applyD1Migrations from cloudflare:test

The `applyD1Migrations(db: D1Database, migrations: D1Migration[], migrationTableName?: string)` function applies all un-applied D1 migrations stored in the `migrations` array to database `db`, recording migrations state in the `migrationTableName` table. `migrationTableName` defaults to `d1_migrations`. Use the `readD1Migrations()` function from the `@cloudflare/vitest-pool-workers/config` package inside Node.js to get the `migrations` array.

introspectWorkflowInstance from cloudflare:test

The `introspectWorkflowInstance(workflow: Workflow, instanceId: string)` function creates an introspector for a specific Workflow instance with a known ID, used to modify its behavior, await outcomes, and clear its state during tests. The returned `WorkflowInstanceIntrospector` provides methods: `modify()`, `waitForStepResult()`, `waitForStatus()`, `getOutput()`, `getError()`, `dispose()`, and `[Symbol.asyncDispose]()`. When using per-file storage isolation, the introspector must be disposed at the end of each test using either `await using` or explicit `dispose()` call.

introspectWorkflow from cloudflare:test

The `introspectWorkflow(workflow: Workflow)` function creates an introspector for a Workflow where instance IDs are unknown beforehand, allowing modifications that apply to all subsequently created instances. The returned `WorkflowIntrospector` provides methods: `modifyAll()`, `get()`, `dispose()`, and `[Symbol.asyncDispose]()`. The introspector captures all instances created after initialization and must be disposed at the end of each test.

env binding usage in tests example

Example of using env bindings in tests: 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'); });

exports.default.fetch() integration test example

Example of writing integration tests against a Worker's default export: import { exports } from 'cloudflare:workers'; it('dispatches fetch event', async () => { const response = await exports.default.fetch('https://example.com'); expect(await response.text()).toMatchInlineSnapshot(...); });

Fetch handler test example with execution context

Example of testing a fetch handler with execution context: 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(...); });

Scheduled handler test example

Example of testing a scheduled handler: 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); });

Queue handler test example

Example of testing a queue handler: 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 Object test example with runInDurableObject

Example of testing a Durable Object: 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'); });

Durable Object eviction test example

Example of testing Durable Object eviction: import { env } from 'cloudflare:workers'; import { evictDurableObject } from 'cloudflare:test'; import { it, expect } from 'vitest'; it('preserves stored data across eviction', async () => { const id = env.COUNTER.idFromName('evict-test'); const stub = env.COUNTER.get(id); expect(await (await stub.fetch('https://example.com')).text()).toBe('1'); expect(await (await stub.fetch('https://example.com')).text()).toBe('2'); await evictDurableObject(stub); expect(await (await stub.fetch('https://example.com')).text()).toBe('3'); });

listDurableObjectIds test example

Example of listing Durable Object IDs: 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); });

reset cleanup example

Example of using reset for test cleanup: import { reset } from 'cloudflare:test'; import { afterEach } from 'vitest'; afterEach(async () => { await reset(); });

evictAllDurableObjects cleanup example

Example of using evictAllDurableObjects for test cleanup: import { evictAllDurableObjects } from 'cloudflare:test'; import { afterEach } from 'vitest'; afterEach(async () => { await evictAllDurableObjects(); });

introspectWorkflow test example

Example of testing Workflow instances with introspectWorkflow: import { env, exports } from 'cloudflare:workers'; import { introspectWorkflow } from 'cloudflare:test'; it('should disable all sleeps, mock an event and complete', async () => { 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' } }); }); await env.MY_WORKFLOW.create(); 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 }); } });

WorkflowInstanceModifier comprehensive example

Example showcasing all WorkflowInstanceModifier functions: import { env } from 'cloudflare:workers'; import { introspectWorkflowInstance } from 'cloudflare:test'; it('should apply all modifier functions', async () => { const instance = await introspectWorkflowInstance(env.COMPLEX_WORKFLOW, '123456'); try { await instance.modify(async (m) => { await m.disableSleeps(); await m.disableRetryDelays(); await m.mockStepResult({ name: 'get-order-details' }, { orderId: 'abc-123', amount: 99.99 }); await m.mockEvent({ type: 'user-approval', payload: { approved: true, approverId: 'user-123' } }); await m.mockStepError({ name: 'process-payment' }, new Error('Payment gateway timeout'), 1); await m.forceStepTimeout({ name: 'notify-shipping-partner' }); await m.forceEventTimeout({ name: 'wait-for-fraud-check' }); }); await env.COMPLEX_WORKFLOW.create({ id: '123456' }); expect(await instance.waitForStepResult({ name: 'get-order-details' })).toEqual({ orderId: 'abc-123', amount: 99.99 }); 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 { await instance.dispose(); } });

Test version affinity with repeated requests using same key

Verify that version affinity is working by sending multiple requests with the same version key and confirming they are handled by the same version. Example: `curl -s https://example.com -H 'Cloudflare-Workers-Version-Key: test-user-123'` should return responses from the same version when run multiple times.

Use version metadata binding to verify version affinity during testing

Use the version metadata binding to include the version ID in your Worker's response during testing to verify that version affinity is working correctly.

cloudflareTest() Vite plugin configuration

The cloudflareTest() function is a Vite plugin that configures Vitest to use the Workers integration with correct module resolution settings. It is imported from @cloudflare/vitest-pool-workers and added to the plugins array in the Vitest config alongside defineConfig() from Vitest. It accepts configuration options and can accept an optional async function returning options.

Custom Vitest environments and runners not supported

Custom Vitest environment or runner configurations are not supported when using the Workers Vitest integration.

buildPagesASSETSBinding() function

buildPagesASSETSBinding(assetsPath) is exported from @cloudflare/vitest-pool-workers/config. It creates a Pages ASSETS binding that serves files inside the provided assetsPath directory. This is required if you use createPagesEventContext() to test Pages Functions.

readD1Migrations() function

readD1Migrations(migrationsPath) is exported from @cloudflare/vitest-pool-workers/config. It reads all D1 migrations stored at migrationsPath and returns them ordered by migration number. Each migration has its contents split into an array of individual SQL queries. Use applyD1Migrations() inside a test or setup file to apply the migrations.

CloudflareTestOptions.main configuration

The main option is a string (optional) that specifies the entry point to Worker run in the same isolate/context as tests. This option is required to use Durable Objects without an explicit scriptName if classes are defined in the same Worker. The file goes through Vite transforms and can be TypeScript. Importing the module inside tests with import module from "<path-to-main>" gives the same module instance as used internally for exports and Durable Object bindings. If wrangler.configPath is defined and main is not, it will be read from the main field in the configuration file.

CloudflareTestOptions.miniflare configuration

The miniflare option (optional) accepts SourcelessWorkerOptions & { workers?: WorkerOptions[] }. Use this to provide configuration information typically stored in the Wrangler configuration file, such as bindings, compatibility dates, and compatibility flags. If no compatibility_date is provided, the test will use the latest locally available date. For multiple Workers, configure auxiliary Workers using the workers array with regular Miniflare WorkerOptions objects.

Dynamic configuration with inject function

You can pass an async function to cloudflareTest() that receives an inject function. This allows you to define miniflare configuration based on injected values from globalSetup scripts. Use this when you have a value that is dynamically generated and only known at runtime, such as a server port. The injected value can be used in external service bindings or Hyperdrive configuration.

Example dynamic configuration with inject

Example using inject with globalSetup to dynamically configure Hyperdrive: ```ts // env.d.ts declare module "vitest" { interface ProvidedContext { port: number; } } // global-setup.ts import type { GlobalSetupContext } from "vitest/node"; export default function ({ provide }: GlobalSetupContext) { provide("port", 1337); return () => { /* ...then teardown here */ }; } // vitest.config.ts import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ cloudflareTest(({ inject }) => ({ miniflare: { hyperdrives: { DATABASE: `postgres://user:••••@example.com:${inject("port")}/db`, }, }, })), ], test: { globalSetup: ["./global-setup.ts"], }, }); ```

Example readD1Migrations usage

Example using readD1Migrations: ```ts import path from "node:path"; import { cloudflareTest, readD1Migrations } from "@cloudflare/vitest-pool-workers"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ cloudflareTest(async () => { const migrationsPath = path.join(__dirname, "migrations"); const migrations = await readD1Migrations(migrationsPath); return { miniflare: { bindings: { TEST_MIGRATIONS: migrations }, }, }; }), ], test: { setupFiles: ["./test/apply-migrations.ts"], }, }); ```

Example buildPagesASSETSBinding usage

Example using buildPagesASSETSBinding: ```ts import path from "node:path"; import { buildPagesASSETSBinding, cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ cloudflareTest(async () => { const assetsPath = path.join(__dirname, "public"); return { miniflare: { serviceBindings: { ASSETS: await buildPagesASSETSBinding(assetsPath), }, }, }; }), ], }); ```

Auxiliary Workers restrictions

Auxiliary Workers configured in the miniflare workers array have the following restrictions: they cannot have TypeScript entrypoints and must be compiled to JavaScript first; they use regular Workers module resolution semantics; they cannot access the cloudflare:test module; they do not require specific compatibility dates or flags; they can be written with Service Worker syntax; and they are not affected by global mocks defined in tests.

CloudflareTestOptions.wrangler configuration

The wrangler option (optional) accepts { configPath?: string; environment?: string }. configPath is the path to the Wrangler configuration file to load main, compatibility settings, and bindings from. These options are merged with the miniflare option, with miniflare values taking precedence. The configPath accepts both .toml and .json files. The environment option specifies the Wrangler environment to pick up bindings and variables from.

Example Vitest configuration with cloudflareTest

Example Vitest configuration: ```ts import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc", }, }), ], }); ```

SourcelessWorkerOptions type definition

SourcelessWorkerOptions is a type that omits script, scriptPath, modules, and modulesRoot properties from the Miniflare WorkerOptions type. It is defined as: type SourcelessWorkerOptions = Omit<WorkerOptions, "script" | "scriptPath" | "modules" | "modulesRoot">.

Give your agent this brain