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 3 of 5.

Example: Debug output on test failure

const server = createTestHarness({ workers: [{ configPath: "./wrangler.jsonc" }], }); afterEach(({ task }) => { if (task.result?.state === "fail") { server.debug(); } });

createTestHarness() runs Workers in a local server

createTestHarness() runs one or more Workers in a single local server. Each Worker can come from a Wrangler project or a Vite project that uses the Cloudflare Vite plugin.

Configure Worker projects with configPath in createTestHarness

Point each entry in the workers array to the Wrangler configuration file for a project using the configPath property.

createTestHarness import and basic setup

Import createTestHarness from 'wrangler' and create a test harness by calling it with a configuration object containing a workers array. Each worker in the array requires a configPath property pointing to the Wrangler configuration file, such as './wrangler.jsonc'.

Test harness lifecycle management with Vitest

Manage test harness lifecycle using Vitest hooks: call await server.listen() in beforeAll to start the server before all tests, call await server.reset() in afterEach to recreate storage and restore original Worker options after each test, and call await server.close() in afterAll to close the server after all tests are complete.

server.fetch() method for testing Workers

Use server.fetch() to send HTTP requests to the Worker during tests. Pass a path string like '/' to server.fetch(), which returns a Response object. Call await response.text() to get the response body as a string for assertions.

Testing prerequisites for Workers integration tests

To write integration tests for a Cloudflare Worker using createTestHarness, you need: a Worker project with a Wrangler configuration file, a Node.js test runner such as Vitest, and wrangler installed as a development dependency.

Test harness configuration with configPath

When creating a test harness with createTestHarness(), pass an object with a workers property containing an array of worker configurations. Each worker configuration requires a configPath property that points to the Wrangler configuration file path.

createTestHarness API for integration testing

createTestHarness() is a Wrangler API that enables integration testing for Cloudflare Workers from any Node.js test runner. It can run one or more Workers from Wrangler projects or Vite projects that use the Cloudflare Vite plugin.

Test harness capabilities

The test harness runs production build output from Wrangler or the Cloudflare Vite plugin, dispatches requests and events to one or more Workers, provides access to bindings and local storage from tests, and captures logs and diagnostic output from the Workers runtime.

Test harness integrations

The test harness can be integrated with MSW (Mock Service Worker) and Playwright for testing purposes.

Interacting with Workers in tests

Tests can interact with Workers using the test harness by testing routes, dispatching events, controlling Workflows, and asserting logs.

Test state preparation with test harness

The test harness supports preparing test state by seeding storage, mocking outbound requests, and replacing bindings. This is covered in the 'Prepare test state' guide.

Test harness configuration topics

The test harness can be configured for Workers, test values, lifecycle hooks, and failure diagnostics. Additional configuration is covered in the 'Configure the test harness' guide.

introspectWorkflowInstance example with mockStepResult

Example showing introspectWorkflowInstance usage to mock step results: const instance = await worker.introspectWorkflowInstance( "MY_WORKFLOW", "instance-id", ); await instance.modify(async (modifier) => { await modifier.mockStepResult({ name: "load-user" }, { id: "123" }); }); await instance.waitForStatus("complete");

getWorker to bypass route matching

Use server.getWorker(name) to target a specific Worker directly and bypass route matching. This allows testing a single Worker in isolation or triggering specific event handlers.

createTestHarness multiple workers routing

The createTestHarness accepts a workers array where the first Worker is the primary Worker. server.fetch() with relative URLs routes to the primary Worker. Absolute URLs are matched against configured routes from each Worker's wrangler.jsonc. If no route matches, requests fall back to the primary Worker.

Test harness multiple workers example

Example showing how to set up test harness with multiple Workers: const server = createTestHarness({ workers: [ /** Includes "routes": ["example.com/*"] */ { configPath: "./workers/web/wrangler.jsonc" }, /** Includes "routes": ["api.example.com/v1/*"] */ { configPath: "./workers/api/wrangler.jsonc" }, ], }); const primaryResponse = await server.fetch("/"); const apiResponse = await server.fetch("http://api.example.com/v1/users/123"); const webResponse = await server.fetch("http://example.com/users/123");

Direct worker fetch and scheduled events example

Example showing how to use getWorker to interact with a specific Worker: const apiWorker = server.getWorker("api-worker"); const response = await apiWorker.fetch("http://api.example.com/v1/users/123"); await apiWorker.scheduled({ cron: "0 0 * * *", scheduledTime: new Date(), });

Test harness log capture and assertions

The test harness captures logs from the Workers runtime. Use server.getLogs() to retrieve log entries for assertions. Logs are reset when calling server.reset(). Call server.clearLogs() to isolate logs between specific actions.

Assert logged behavior with getLogs example

Example asserting logged behavior in test harness: test("logs scheduled job results", async ({ expect }) => { const apiWorker = server.getWorker("api-worker"); await apiWorker.scheduled({ cron: "0 0 * * *", scheduledTime: new Date("2026-05-29T00:00:00.000Z"), }); expect(server.getLogs()).toEqual([ expect.objectContaining({ level: "info", message: "Generated daily report for 2026-05-29", }), ]); server.clearLogs(); await apiWorker.scheduled({ cron: "0 0 * * *", scheduledTime: new Date("2026-05-30T00:00:00.000Z"), }); expect(server.getLogs()).toEqual([ expect.objectContaining({ level: "info", message: "Generated daily report for 2026-05-30", }), ]); });

introspectWorkflow to control Workflow execution

Use worker.introspectWorkflow(bindingName) to control new Workflow instances and inspect their state. This allows modifying Workflow behavior during testing, such as disabling sleep steps and mocking step results.

introspectWorkflow example with modifyAll

Example showing introspectWorkflow usage to control Workflow execution: const worker = server.getWorker<ApiEnv>("api-worker"); await using workflow = await worker.introspectWorkflow("MY_WORKFLOW"); await workflow.modifyAll(async (modifier) => { await modifier.disableSleeps([{ name: "wait-for-approval" }]); }); await worker.fetch("/start-workflow"); const [instance] = await workflow.get(); await instance.waitForStatus("complete"); expect(await instance.getOutput()).toEqual({ approved: true });

introspectWorkflowInstance for known instance ID

Use worker.introspectWorkflowInstance(bindingName, instanceId) to introspect a specific Workflow instance when the instance ID is already known. This allows controlling that particular instance for testing.

Playwright fixture baseURL configuration

The baseURL fixture in a Playwright test setup with createTestHarness should retrieve the URL from server.listen() and use url.href to set the base URL for Playwright tests.

Playwright fixture with createTestHarness setup

Example Playwright fixture that integrates createTestHarness for testing Workers: ```ts import { test as base, expect } from "@playwright/test"; import { http, HttpResponse } from "msw"; import { setupServer, type SetupServerApi } from "msw/node"; import { createTestHarness, type TestHarness } from "wrangler"; type TestFixtures = { reset: void; }; type WorkerFixtures = { network: SetupServerApi; server: TestHarness; }; const test = base.extend<TestFixtures, WorkerFixtures>({ network: [ async ({}, use) => { const network = setupServer(); network.listen({ onUnhandledRequest: "error" }); await use(network); network.close(); }, { scope: "worker" }, ], server: [ async ({}, use) => { const server = createTestHarness({ workers: [ { configPath: "./dist/web_worker/wrangler.json" }, { configPath: "./dist/api_worker/wrangler.json" }, ], }); await server.listen(); await use(server); await server.close(); }, { scope: "worker" }, ], baseURL: async ({ server }, use) => { const { url } = await server.listen(); await use(url.href); }, reset: [ async ({ network, server }, use, testInfo) => { await use(); if (testInfo.status !== testInfo.expectedStatus) { server.debug(); } network.resetHandlers(); await server.reset(); }, { auto: true }, ], }); test("renders a user profile", async ({ page, network }) => { network.use( http.get("http://identity.example.com/profile/:id", ({ params }) => { return HttpResponse.json({ id: params.id, name: "Ada" }); }), ); await page.goto("/users/123"); await expect(page.getByText("Profile: Ada")).toBeVisible(); }); ```

Mock Service Worker example with createTestHarness

Example showing how to use Mock Service Worker with createTestHarness to intercept fetch requests: ```ts import { afterAll, afterEach, beforeAll, test } from "vitest"; import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { createTestHarness } from "wrangler"; const network = setupServer(); const server = createTestHarness({ workers: [{ configPath: "./wrangler.jsonc" }], }); beforeAll(async () => { network.listen({ onUnhandledRequest: "error" }); await server.listen(); }); afterEach(async () => { network.resetHandlers(); await server.reset(); }); afterAll(async () => { network.close(); await server.close(); }); test("loads a user profile", async ({ expect }) => { network.use( http.get("http://identity.example.com/profile/:id", ({ params }) => { return HttpResponse.json({ id: params.id, name: "Ada" }); }), ); const worker = server.getWorker(); const response = await worker.fetch("/users/123"); expect(await response.json()).toEqual({ id: "123", name: "Ada" }); }); ```

createTestHarness with Mock Service Worker (MSW) integration

Mock Service Worker can be used with createTestHarness to intercept outbound fetch() requests from Workers and return predictable responses. MSW provides reusable request handlers that can be shared across tests. The pattern involves calling network.listen() with onUnhandledRequest: 'error' in beforeAll, using network.use() in tests to set up handlers, calling network.resetHandlers() in afterEach to reset handlers after each test, and network.close() in afterAll to clean up.

createTestHarness multiple workers configuration

createTestHarness can be configured with multiple workers by passing an array to the workers property, with each entry specifying a configPath pointing to a wrangler.json or wrangler.jsonc file for each worker.

createTestHarness with Playwright integration

Playwright can be used with createTestHarness to verify user flows in a real browser and test Workers projects end-to-end. Playwright can navigate pages, interact with the user interface, and verify behavior. A Playwright fixture can start a test server with createTestHarness before browser tests, and can also use Mock Service Worker to intercept outbound fetch() requests at the same time.

Playwright fixture debug and reset pattern

In a Playwright fixture with createTestHarness, the reset fixture should call server.debug() when testInfo.status does not match testInfo.expectedStatus to help diagnose test failures, then reset both network handlers and server state for the next test.

unstable_startWorker() accepts config parameter

The unstable_startWorker() function accepts a config parameter that specifies the path to a Wrangler configuration file. The file will be automatically loaded and used to configure the worker.

unstable_startWorker() returns worker object with fetch() and dispose() methods

The unstable_startWorker() function returns a worker object. The worker object has a fetch() method for making requests and a dispose() method for cleanup. Both methods are asynchronous.

unstable_startWorker() is deprecated

The unstable_startWorker() API is deprecated. Cloudflare recommends using the createTestHarness() API instead, which provides a harness specifically designed for integration testing.

unstable_startWorker() API overview

The unstable_startWorker() API exposes the internals of the Wrangler dev server and allows customization of how it runs. Unlike using Miniflare directly for testing, you can pass in a Wrangler configuration file and it will automatically load the configuration.

unstable_startWorker() basic usage example

Example showing how to use unstable_startWorker() with node:test framework: ```ts import assert from "node:assert"; import test, { after, before, describe } from "node:test"; import { unstable_startWorker } from "wrangler"; describe("worker", () => { let worker; before(async () => { worker = await unstable_startWorker({ config: "wrangler.json" }); }); test("hello world", async () => { assert.strictEqual( await (await worker.fetch("http://example.com")).text(), "Hello world", ); }); after(async () => { await worker.dispose(); }); }); ``` This example demonstrates: calling unstable_startWorker() with a wrangler.json config file, using worker.fetch() to make requests, and calling worker.dispose() to cleanup.

Execute SQL in Durable Object storage from tests

Use storage.exec() to execute SQL queries against a Durable Object's SQLite storage. For example: await storage.exec("INSERT INTO counters (id, value) VALUES (?, ?)", "user-123", 0); You can also query: const rows = await storage.exec<{ value: number }>("SELECT value FROM counters WHERE id = ?", "user-123");

Mock outbound fetch() requests in tests

The test harness proxies outbound fetch() requests from Workers through globalThis.fetch() in your Node environment. You can intercept these requests and return predictable responses using vi.spyOn() or Mock Service Worker (MSW).

Mock outbound fetch requests with vi.spyOn()

Use vi.spyOn(globalThis, "fetch").mockImplementation() to intercept fetch requests. Example: vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => { const request = new Request(input, init); if (request.url === "http://identity.example.com/profile/123") { return Response.json({ id: "123", name: "Ada" }); } throw new Error(`Unexpected request: ${request.method} ${request.url}`); });

bindingOverrides routes bindings to test Workers

Use bindingOverrides when creating a test harness to control binding behavior. It routes the binding to a test Worker running inside the harness. For example, a test Worker can replace the Browser Rendering binding and return a known screenshot without starting a browser.

Seed KV storage from tests using getEnv()

Access KV bindings through worker.getEnv() and use the put() method to seed data directly from a test. For example: await env.USERS.put("123", JSON.stringify({ name: "Ada" }));

getEnv() returns configured bindings and variables

Call worker.getEnv() to retrieve the variables, secrets, and bindings configured for a Worker. You can specify types for the worker handle so these values are typed. The returned environment object contains all configured storage bindings.

Access test Worker exports with getExport()

Call worker.getExport() to access the default export from a test Worker. This allows you to call JSRPC methods that configure the test Worker's behavior or assert its state.

Mock Browser Rendering binding with test Worker

Create a test Worker to replace the Browser Rendering binding. Configure it with bindingOverrides: { BROWSER: "mock-browser" }. Access the mock Worker with server.getWorker<unknown, typeof import("../workers/mock-browser")>("mock-browser").getExport() to configure its behavior, such as calling await mockBrowser.setScreenshot() to return a known screenshot.

applyD1Migrations() applies configured D1 migrations

Call worker.applyD1Migrations(bindingName) to read migration settings for a D1 binding from the Wrangler configuration. It uses the configured migrations_dir and migrations_pattern. Without these options, it reads .sql files from the migrations directory relative to the configuration file. Call it after storage is reset to apply migrations that have not already run.

Seed D1 database from tests after applying migrations

After calling applyD1Migrations(), access the database with worker.getEnv() and use prepare().bind().run() to insert test data. For example: await env.DATABASE.prepare("INSERT INTO daily_reports (date, user_ids) VALUES (?, ?)").bind("2026-05-29", JSON.stringify(["123", "456"])).run();

getDurableObjectStorage() accesses Durable Object SQLite storage

Call worker.getDurableObjectStorage() to access the storage of a SQLite-backed Durable Object instance. Pass its binding name or exported class name, then select the instance by name or ID. The returned handle executes SQL inside the Durable Object to seed instances before tests or inspect state after the Worker runs.

Vitest inspector configuration in config file

In the Vitest configuration file, you can set a custom inspector port using the following structure: export default defineConfig({ test: { inspector: { port: 3456 } } }).

Enable Vitest debugging with inspector

To debug Workers tests with Vitest, run the command 'vitest --inspect --no-file-parallelism' and attach a debugger to port 9229.

Customize Vitest inspector port

To use a different inspector port, run 'vitest --inspect=<port> --no-file-parallelism' or define it in the Vitest configuration file using the test.inspector.port option.

VS Code launch.json configuration for debugging Workers tests

To debug Workers tests in VS Code, configure .vscode/launch.json with: 1. 'Open inspector with Vitest' configuration: type 'node', request 'launch', program '${workspaceRoot}/node_modules/vitest/vitest.mjs', console 'integratedTerminal', args ['--inspect=9229', '--no-file-parallelism']. 2. 'Attach to Workers Runtime' configuration: type 'node', request 'attach', port 9229, cwd '/', resolveSourceMapLocations null, attachExistingChildren false, autoAttachChildProcesses false. 3. Compound configuration 'Debug Workers tests' that includes both configurations with stopAll true.

VS Code breakpoint debugging for Workers tests

Create a .vscode/launch.json file with two configurations: 'Open inspector with Vitest' which launches vitest with --inspect=9229 --no-file-parallelism, and 'Attach to Workers Runtime' which attaches to port 9229. Combine them in a compound configuration named 'Debug Workers tests' that launches both sequentially.

Add breakpoints after starting VS Code debugger

After selecting 'Debug Workers tests' from the Run & Debug panel in VS Code, which opens an inspector with Vitest and attaches a debugger to the Workers runtime, you can add breakpoints to test files to start debugging.

Vitest debugging requires @cloudflare/vitest-pool-workers v0.7.5 or later

The debugging functionality described is available with @cloudflare/vitest-pool-workers version 0.7.5 or later.

Workers Vitest integration capabilities

The Workers Vitest integration supports unit tests and integration tests, provides direct access to Workers runtime APIs and bindings, implements isolated per-test-file storage, runs tests fully-locally using Miniflare, leverages Vitest's hot-module reloading for near instant reruns, and supports projects with multiple Workers.

Workers Vitest integration overview

Cloudflare recommends using the Workers Vitest integration for unit testing Workers and Pages Functions projects. Vitest is a popular JavaScript testing framework featuring fast watch mode, Jest compatibility, and out-of-the-box TypeScript support. The integration provides a custom pool that allows Vitest tests to run inside the Workers runtime.

Behavior difference with nodejs_compat flag during testing vs deployment

Using Vitest Pool Workers may cause your Worker to behave differently when deployed than during testing because the nodejs_compat flag is enabled by default during testing. This means Node.js-specific APIs and modules are available when running tests, but Cloudflare Workers do not support these Node.js APIs in the production environment unless you specify the nodejs_compat flag in your Worker configuration.

Vitest Worker integration test execution flow

When you run tests with the Workers Vitest integration, Vitest performs the following steps: (1) reads and evaluates the configuration file using Node.js, (2) runs any globalSetup files using Node.js, (3) collects and sequences test files, (4) for each Vitest project, starts one or more workerd processes depending on its configured isolation and concurrency, each running one or more Workers, (5) runs setupFiles and test files in workerd using the appropriate Workers, and (6) watches for changes and re-runs test files using the same Workers if the configuration has not changed.

Storage isolation model in Vitest Worker integration

Storage isolation in the Workers Vitest integration is per test file. Each test file gets its own storage environment, and any writes to storage during a test file are not visible to other test files. The integration reuses Workers and their module caches between test runs where possible. A copy of all auxiliary workers exists in each workerd process.

Running test files concurrently vs serially in Vitest Worker integration

By default, test files run concurrently in the Workers Vitest integration. To make test files share the same storage (for example, for integration tests that depend on shared state), use the Vitest flags --max-workers=1 --no-isolate.

Give your agent this brain