diff --git a/src/content/changelog/workers/2026-06-16-test-harness-api.mdx b/src/content/changelog/workers/2026-06-16-test-harness-api.mdx new file mode 100644 index 00000000000..68e19270a09 --- /dev/null +++ b/src/content/changelog/workers/2026-06-16-test-harness-api.mdx @@ -0,0 +1,81 @@ +--- +title: Test Workers the way they are built with createTestHarness +description: Run integration tests against your Workers' production build output from any Node.js test runner. +products: + - workers +date: 2026-06-16 +publish_future_dated_entry: true +--- + +import { TypeScriptExample } from "~/components"; + +Wrangler now provides `createTestHarness()`, an API for running integration tests against Workers from any Node.js test runner. It works with Wrangler projects and [projects built with the Cloudflare Vite plugin](/workers/testing/test-harness/examples/#test-workers-built-by-the-cloudflare-vite-plugin). + +Use `createTestHarness()` when you want tests to run against Workers built by Wrangler or the Cloudflare Vite plugin. The test harness starts a local Worker server and [provides helpers for dispatching requests, resetting storage, and inspecting runtime logs](/workers/wrangler/api/#createtestharness). + +This is useful for tests that need to: + +- [Route requests across multiple Workers](/workers/testing/test-harness/examples/#test-multiple-workers-and-route-dispatch). +- [Mock outbound `fetch()` requests](/workers/testing/test-harness/examples/#mock-outbound-requests) with Node.js request mocking libraries such as [MSW](https://mswjs.io/). +- [Run Playwright tests against a Worker started by the test harness](/workers/testing/test-harness/examples/#use-with-playwright). + +For example, this test starts two Workers and mocks an upstream API: + + + +```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: [ + /** Includes `"routes": ["example.com/*"]` */ + { configPath: "./workers/web/wrangler.jsonc" }, + /** Includes `"routes": ["api.example.com/v1/*"]` */ + { configPath: "./workers/api/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("routes requests to each Worker", async ({ expect }) => { + network.use( + http.get("http://identity.example.com/profile/:id", ({ params }) => { + return HttpResponse.json({ id: params.id, name: "Ada" }); + }) + ); + + const apiResponse = await server.fetch( + "http://api.example.com/v1/users/123" + ); + expect(await apiResponse.json()).toEqual({ + id: "123", + name: "Ada", + }); + + const webResponse = await server.fetch( + "http://example.com/users/123" + ); + expect(await webResponse.text()).toBe("Profile: Ada"); +}); +``` + + + +Refer to our [Test harness guide](/workers/testing/test-harness/) for more details. diff --git a/src/content/docs/workers/testing/index.mdx b/src/content/docs/workers/testing/index.mdx index bf3b4465776..2eb84a6ca6f 100644 --- a/src/content/docs/workers/testing/index.mdx +++ b/src/content/docs/workers/testing/index.mdx @@ -1,37 +1,35 @@ --- pcx_content_type: navigation title: Testing -description: Compare testing options for Cloudflare Workers, including Vitest integration, Miniflare, and unstable_startWorker. +description: Choose testing tools for Cloudflare Workers, including createTestHarness and the Vitest integration. sidebar: order: 14 products: - workers --- -import { Render, LinkButton } from "~/components"; +The Workers platform offers multiple testing tools for different parts of your application. For most projects, use the [Workers Vitest integration](/workers/testing/vitest-integration/) for unit tests and [`createTestHarness()`](/workers/testing/test-harness/) for integration tests. These tools can be used together in the same project. -The Workers platform has a variety of ways to test your applications, depending on your requirements. We recommend using the [Vitest integration](/workers/testing/vitest-integration), which allows you to run tests _inside_ the Workers runtime, and unit test individual functions within your Worker. +## Unit tests - - Get started with Vitest - +Use the [Workers Vitest integration](/workers/testing/vitest-integration/) when you want fast feedback on functions and modules. Tests run inside the Workers runtime, so your test code can access bindings and runtime APIs directly. -## Testing comparison matrix +The Workers Vitest integration provides: -However, if you don't use Vitest, both [Miniflare's API](/workers/testing/miniflare/writing-tests) and the [`unstable_startWorker()`](/workers/wrangler/api/#unstable_startworker) API provide options for testing your Worker in any testing framework. +- Fast feedback while testing individual functions and modules. +- Direct assertions against binding state, such as values written to KV, R2, D1, or Durable Objects. +- Direct calls to Durable Objects and other runtime APIs. -| Feature | [Vitest integration](/workers/testing/vitest-integration) | [`unstable_startWorker()`](/workers/testing/unstable_startworker/) | [Miniflare's API](/workers/testing/miniflare/writing-tests/) | -| ------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------ | -| Unit testing | ✅ | ❌ | ❌ | -| Integration testing | ✅ | ✅ | ✅ | -| Loading Wrangler configuration files | ✅ | ✅ | ❌ | -| Use bindings directly in tests | ✅ | ❌ | ✅ | -| Isolated per-test storage | ✅ | ❌ | ❌ | -| Outbound request mocking | ✅ | ❌ | ✅ | -| Multiple Worker support | ✅ | ✅ | ✅ | -| Direct access to Durable Objects | ✅ | ❌ | ❌ | -| Run Durable Object alarms immediately | ✅ | ❌ | ❌ | -| List Durable Objects | ✅ | ❌ | ❌ | -| Testing service Workers | ❌ | ✅ | ✅ | +To set up unit tests, refer to [Write your first Vitest test](/workers/testing/vitest-integration/write-your-first-test/). - +## Integration tests + +Use [`createTestHarness()`](/workers/testing/test-harness/) when you want tests to run against Workers built by Wrangler or the [Cloudflare Vite plugin](/workers/vite-plugin/). It lets any Node.js test runner dispatch requests to a running local Worker. + +`createTestHarness()` provides: + +- Tests that run the production build output produced by Wrangler or Vite. +- Requests dispatched through configured Worker routes. +- Coverage for a multi-Worker application as a whole. + +To set up integration tests, refer to [Write your first integration test](/workers/testing/test-harness/write-your-first-test/). diff --git a/src/content/docs/workers/testing/miniflare/index.mdx b/src/content/docs/workers/testing/miniflare/index.mdx index 4748e6f6d06..bb7ef822ce3 100644 --- a/src/content/docs/workers/testing/miniflare/index.mdx +++ b/src/content/docs/workers/testing/miniflare/index.mdx @@ -3,7 +3,7 @@ title: Miniflare description: Simulate and test Cloudflare Workers locally with Miniflare, a fully-local development simulator. pcx_content_type: navigation sidebar: - order: 16 + order: 17 head: - tag: title content: Miniflare diff --git a/src/content/docs/workers/testing/miniflare/writing-tests.mdx b/src/content/docs/workers/testing/miniflare/writing-tests.mdx index 944bb9a441e..0fa2ee20cf2 100644 --- a/src/content/docs/workers/testing/miniflare/writing-tests.mdx +++ b/src/content/docs/workers/testing/miniflare/writing-tests.mdx @@ -14,7 +14,7 @@ import { TabItem, Tabs, Details, PackageManagers } from "~/components"; import { FileTree } from "@astrojs/starlight/components"; :::note -For most users, Cloudflare recommends using the Workers Vitest integration. If you have been using test environments from Miniflare, refer to the [Migrate from Miniflare 2 guide](/workers/testing/vitest-integration/migration-guides/migrate-from-miniflare-2/). +For most users, Cloudflare recommends using the [Workers Vitest integration](/workers/testing/vitest-integration/) for unit tests and [`createTestHarness()`](/workers/testing/test-harness/) for integration tests. Use Miniflare directly when you need low-level simulator control that is not exposed by those higher-level testing APIs. ::: This guide will show you how to set up [Miniflare](/workers/testing/miniflare) to test your Workers. Miniflare is a low-level API that allows you to fully control how your Workers are run and tested. diff --git a/src/content/docs/workers/testing/test-harness/examples.mdx b/src/content/docs/workers/testing/test-harness/examples.mdx new file mode 100644 index 00000000000..133d52f959a --- /dev/null +++ b/src/content/docs/workers/testing/test-harness/examples.mdx @@ -0,0 +1,310 @@ +--- +pcx_content_type: concept +title: Examples +sidebar: + order: 2 +head: [] +description: Examples for writing integration tests with createTestHarness. +products: + - workers +--- + +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, +} from "~/components"; + +These examples show common patterns for integration testing Workers with `createTestHarness()`. The snippets use Vitest and Playwright, but `createTestHarness()` works with any Node.js test runner. For a complete runnable project that applies these examples, see the [`create-test-harness-example`](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/create-test-harness-example) fixture. + +## Test Workers built by the Cloudflare Vite plugin + +You can start Wrangler projects directly from their Wrangler configuration files. For Workers built by the [Cloudflare Vite plugin](/workers/vite-plugin/), run the Vite build first so tests use the production build output: + + + +Then point `configPath` at the generated Wrangler configuration file: + + + +```ts +const server = createTestHarness({ + workers: [ + { configPath: "./dist/web_worker/wrangler.json" }, + ], +}); +``` + + + +You can also test Workers built by different tools together. Each Worker in the harness is configured independently. + + + +```ts +const server = createTestHarness({ + workers: [ + { configPath: "./workers/api/wrangler.jsonc" }, + { configPath: "./dist/web_worker/wrangler.json" }, + ], +}); +``` + + + +## Mock outbound requests + +Workers started by `createTestHarness()` proxy outbound `fetch()` requests through `globalThis.fetch()`. Use Node.js request mocking libraries, such as [MSW](https://mswjs.io/), to mock upstream services. + + + +```ts +network.use( + http.get("http://identity.example.com/profile/:id", ({ params }) => { + return HttpResponse.json({ id: params.id, name: "Ada" }); + }) +); +``` + + + +## Interact with a Worker directly + +Use `server.getWorker(name)` when a test should bypass route matching and call one Worker directly. Worker handles can dispatch `fetch()` events and supported non-HTTP events, such as `scheduled()`. + + + +```ts +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(), +}); +``` + + + +## Override variables and secrets + +Use test-only `vars` and `secrets` to make test inputs explicit or pass values created during test setup. These values override values from the Wrangler configuration file, `.env`, and `.dev.vars`. + + + +```ts +const server = createTestHarness({ + workers: [ + { + configPath: "./wrangler.jsonc", + vars: { API_HOST: "http://identity.example.com" }, + secrets: { API_TOKEN: "test-token" }, + }, + ], +}); +``` + + + +## Configure the harness after setup + +Call `createTestHarness()` without options when the Worker configuration depends on setup work that runs later in your test lifecycle. Use `server.update()` to configure the harness before calling `server.listen()`. When you call `server.reset()`, the harness restarts with the options used to start the current test session. + + + +```ts +const server = createTestHarness(); +let upstream: { url: string; close(): Promise }; + +beforeAll(async () => { + upstream = await startLocalApi(); + + await server.update({ + workers: [ + { + configPath: "./wrangler.jsonc", + vars: { API_HOST: upstream.url }, + }, + ], + }); + + await server.listen(); +}); + +afterEach(async () => { + await server.reset(); +}); + +afterAll(async () => { + await server.close(); + await upstream.close(); +}); +``` + + + +## Test multiple Workers and route dispatch + +When a test harness runs multiple Workers, `server.fetch()` matches requests against the routes defined in each Worker configuration. Add each Worker that should run in the harness to the `workers` array. + +Start the harness with both configuration files: + + + +```ts +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"); +``` + + + +Relative URLs, such as `/`, resolve against the local server URL. Absolute URLs keep their hostname and path, so the request to `api.example.com/v1/users/123` matches `api-worker`, and the request to `example.com/users/123` matches `web-worker`. + +If no configured route matches, the request is dispatched to the primary Worker, which is the first Worker in the `workers` array. + +## Print debug output when tests fail + +Use `server.debug()` in a test runner failure or cleanup hook to print the server timeline and captured Workers runtime logs. This is useful when a test fails and you need to see which requests and runtime logs led to the failure. + +The exact hook depends on your test runner. For example, with Vitest: + + + +```ts +const server = createTestHarness({ + workers: [{ configPath: "./wrangler.jsonc" }], +}); + +afterEach(({ task }) => { + if (task.result?.state === "fail") { + server.debug(); + } +}); +``` + + + +## Assert against Worker logs + +Use `server.getLogs()` when you want to test Worker logic that writes runtime logs. Captured logs are reset when you call `server.reset()`. Use `server.clearLogs()` when one test needs to separate logs from setup work and the behavior it asserts. + + + +```ts +test("runs scheduled jobs and stores the result", async ({ expect }) => { + const apiWorker = server.getWorker("api-worker"); + await apiWorker.fetch("http://api.example.com/v1/users/123"); + await apiWorker.fetch("http://api.example.com/v1/users/456"); + server.clearLogs(); + + await apiWorker.scheduled({ + cron: "0 0 * * *", + scheduledTime: new Date("2026-05-29T00:00:00.000Z"), + }); + + expect(server.getLogs()).toContainEqual( + expect.objectContaining({ + level: "info", + message: "Generated daily report for 2026-05-29", + }) + ); +}); +``` + + + +## Use with Playwright + +Use a Playwright fixture when you want browser tests to run against a Worker started by `createTestHarness()`. Start MSW in the same Node.js process so Worker outbound requests can be mocked. + +The fixture can set the Playwright `baseURL`, expose MSW as `network`, expose the test harness as `server`, and reset MSW handlers and local Worker storage after each test. + + + +```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({ + 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(); +}); +``` + + diff --git a/src/content/docs/workers/testing/test-harness/index.mdx b/src/content/docs/workers/testing/test-harness/index.mdx new file mode 100644 index 00000000000..2ae24fb8338 --- /dev/null +++ b/src/content/docs/workers/testing/test-harness/index.mdx @@ -0,0 +1,31 @@ +--- +pcx_content_type: navigation +title: Test harness +description: Write integration tests for Cloudflare Workers with the createTestHarness API in Wrangler. +sidebar: + order: 16 +products: + - workers +--- + +import { LinkButton } from "~/components"; + +[`createTestHarness()`](/workers/wrangler/api/#createtestharness) is a Wrangler API for writing integration tests against one or more Workers from any Node.js test runner. Use it when you want tests to run against Workers built by Wrangler or the [Cloudflare Vite plugin](/workers/vite-plugin/). + + + Write your first test + + + + View examples + + +## Features + +- Runs production build output from Wrangler or the Cloudflare Vite plugin. +- Starts one or more Workers from Wrangler configuration files. +- Dispatches requests through configured Worker routes. +- Calls a specific Worker's `fetch()` or `scheduled()` handler directly. +- Overrides variables and secrets for tests. +- Resets local storage between tests. +- Captures runtime logs and prints debug output for failed tests. diff --git a/src/content/docs/workers/testing/test-harness/write-your-first-test.mdx b/src/content/docs/workers/testing/test-harness/write-your-first-test.mdx new file mode 100644 index 00000000000..28af6be5378 --- /dev/null +++ b/src/content/docs/workers/testing/test-harness/write-your-first-test.mdx @@ -0,0 +1,58 @@ +--- +title: Write your first test +pcx_content_type: how-to +sidebar: + order: 1 +head: [] +description: Write your first integration test for a Cloudflare Worker with createTestHarness. +products: + - workers +--- + +import { TypeScriptExample } from "~/components"; + +This guide shows how to write a basic integration test for a Worker with `createTestHarness()`. The example uses Vitest as a regular Node.js test runner and exercises the production build output through Wrangler configuration. + +## Prerequisites + +Before you start, make sure that you have: + +- A Worker project with a [Wrangler configuration file](/workers/wrangler/configuration/). +- A Node.js test runner such as [Vitest](https://vitest.dev/). +- `wrangler` installed as a development dependency. + +## Create a test harness + +Import `createTestHarness()` from `wrangler` and point it at your Worker configuration file. + + + +```ts +import { afterAll, afterEach, beforeAll, test } from "vitest"; +import { createTestHarness } from "wrangler"; + +const server = createTestHarness({ + workers: [{ configPath: "./wrangler.jsonc" }], +}); + +beforeAll(async () => { + await server.listen(); +}); + +afterEach(async () => { + await server.reset(); +}); + +afterAll(async () => { + await server.close(); +}); + +test("responds", async ({ expect }) => { + const response = await server.fetch("/"); + expect(await response.text()).toBe("Hello World"); +}); +``` + + + +`server.listen()` starts the local test server. Use `server.fetch()` to send a request to the primary Worker, which is the first Worker listed in the `workers` array. Reset storage and restore Workers to the original options with `server.reset()` after each test. diff --git a/src/content/docs/workers/testing/unstable_startworker.mdx b/src/content/docs/workers/testing/unstable_startworker.mdx index 0f68674ac7b..3206f382e24 100644 --- a/src/content/docs/workers/testing/unstable_startworker.mdx +++ b/src/content/docs/workers/testing/unstable_startworker.mdx @@ -2,50 +2,42 @@ title: Wrangler's unstable_startWorker() pcx_content_type: concept sidebar: - order: 17 + order: 18 head: [] description: Write integration tests using Wrangler's `unstable_startWorker()` API products: - workers --- -import { Render } from "~/components"; -import { LinkButton } from "@astrojs/starlight/components"; - -:::note -For most users, Cloudflare recommends using the Workers Vitest integration. If you have been using `unstable_dev()`, refer to the [Migrate from `unstable_dev()` guide](/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev/). -::: - :::caution -`unstable_startWorker()` is an experimental API subject to breaking changes. +`unstable_startWorker()` is deprecated. Cloudflare recommends using the [`createTestHarness()`](/workers/testing/test-harness/) API, which provides a harness specifically designed for integration testing. ::: -If you do not want to use Vitest, consider using [Wrangler's `unstable_startWorker()` API](/workers/wrangler/api/#unstable_startworker). This API exposes the internals of Wrangler's dev server, and allows you to customise how it runs. Compared to using [Miniflare directly for testing](/workers/testing/miniflare/writing-tests/), you can pass in a Wrangler configuration file, and it will automatically load the configuration for you. +The [`unstable_startWorker()`](/workers/wrangler/api/#unstable_startworker) API exposes the internals of the Wrangler dev server, and allows you to customize how it runs. Compared to using [Miniflare directly for testing](/workers/testing/miniflare/writing-tests/), you can pass in a Wrangler configuration file, and it will automatically load the configuration for you. This example uses `node:test`, but should apply to any testing 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(); - }); - }); - - ``` +```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(); + }); +}); +``` diff --git a/src/content/docs/workers/testing/vitest-integration/index.mdx b/src/content/docs/workers/testing/vitest-integration/index.mdx index 2e037b3ba41..901b1dba15a 100644 --- a/src/content/docs/workers/testing/vitest-integration/index.mdx +++ b/src/content/docs/workers/testing/vitest-integration/index.mdx @@ -10,7 +10,7 @@ products: import { DirectoryListing, Render, LinkButton } from "~/components"; -For most users, Cloudflare recommends using the Workers Vitest integration for testing Workers and [Pages Functions](/pages/functions/) projects. [Vitest](https://vitest.dev/) is a popular JavaScript testing framework featuring a very fast watch mode, Jest compatibility, and out-of-the-box support for TypeScript. In this integration, Cloudflare provides a custom pool that allows your Vitest tests to run _inside_ the Workers runtime. +For most users, Cloudflare recommends using the Workers Vitest integration for unit testing Workers and [Pages Functions](/pages/functions/) projects. [Vitest](https://vitest.dev/) is a popular JavaScript testing framework featuring a very fast watch mode, Jest compatibility, and out-of-the-box support for TypeScript. In this integration, Cloudflare provides a custom pool that allows your Vitest tests to run _inside_ the Workers runtime. The Workers Vitest integration: diff --git a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx index ab98875de5f..1f461b894b0 100644 --- a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx +++ b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx @@ -4,14 +4,18 @@ pcx_content_type: how-to sidebar: order: 3 head: [] -description: Migrate from the - [`unstable_dev`](/workers/wrangler/api/#unstable_dev) API to writing tests - with the Workers Vitest integration. +description: Migrate from the unstable_dev API to writing tests with the Workers Vitest integration. products: - workers --- -The [`unstable_dev`](/workers/wrangler/api/#unstable_dev) API has been a recommended approach to run integration tests. The `@cloudflare/vitest-pool-workers` package integrates directly with Vitest for fast re-runs, supports both unit and integration tests, all whilst providing isolated per-test storage. +:::note + +Cloudflare recommends using the [`createTestHarness()`](/workers/testing/test-harness/) API, which provides a harness specifically designed for integration testing. + +::: + +The [`unstable_dev`](/workers/wrangler/api/#unstable_dev) API has been a recommended approach to run integration tests. The `@cloudflare/vitest-pool-workers` package integrates directly with Vitest for fast re-runs, supports both unit and integration tests, and provides isolated per-test storage. This guide demonstrates key differences between tests written with the `unstable_dev` API and the Workers Vitest integration. For more information on writing tests with the Workers Vitest integration, refer to [Write your first test](/workers/testing/vitest-integration/write-your-first-test/). diff --git a/src/content/docs/workers/wrangler/api.mdx b/src/content/docs/workers/wrangler/api.mdx index 8b28f295cdc..3df0d83311c 100644 --- a/src/content/docs/workers/wrangler/api.mdx +++ b/src/content/docs/workers/wrangler/api.mdx @@ -15,6 +15,7 @@ import { TabItem, Tabs, Type, + TypeScriptExample, MetaInfo, WranglerConfig, PackageManagers, @@ -22,10 +23,174 @@ import { Wrangler offers APIs to programmatically interact with your Cloudflare Workers. +- [`createTestHarness`](#createtestharness) - Start one or more Workers for integration tests in any Node.js test runner. - [`experimental_generateTypes`](#experimental_generatetypes) - Generate TypeScript type definitions from your Worker configuration. -- [`unstable_startWorker`](#unstable_startworker) - Start a server for running integration tests against your Worker. -- [`unstable_dev`](#unstable_dev) - Start a server for running either end-to-end (e2e) or integration tests against your Worker. - [`getPlatformProxy`](#getplatformproxy) - Get proxies and values for emulating the Cloudflare Workers platform in a Node.js process. +- [`unstable_startWorker`](#unstable_startworker) - Deprecated lower-level Wrangler dev server API. +- [`unstable_dev`](#unstable_dev) - Deprecated API for starting a Worker dev server. + +## `createTestHarness` + +Start one or more Workers for integration tests in any Node.js test runner. `createTestHarness()` runs production build output from Wrangler configuration files, Vite-generated Wrangler configuration files, or inline Wrangler configuration objects. It is a higher-level wrapper around Miniflare that provides methods for dispatching requests and scheduled events. + +For setup guidance and examples, refer to [Test harness](/workers/testing/test-harness/). + +### Syntax + + + +```ts +import { createTestHarness } from "wrangler"; + +const server = createTestHarness(options); +``` + + + +### Parameters + +- `options` + + - Test harness options. If you call `createTestHarness()` without options, call `server.update(options)` before `server.listen()`. + + - `root` + + Base directory used to resolve relative Worker configuration paths. Defaults to `process.cwd()`. + + - `workers` + + Workers to run in the test server. The first Worker is the primary Worker. + +Each `WorkerInput` can load a Worker from a Wrangler configuration file: + + + +```ts +const server = createTestHarness({ + workers: [ + { configPath: "./wrangler.web.jsonc" }, + { configPath: "./wrangler.api.jsonc" }, + ], +}); +``` + + + +Configuration file inputs support these fields: + +- `configPath` + - Path to a Wrangler configuration file. Relative paths resolve from `root`. +- `env` + - Wrangler environment to load from the configuration file. +- `vars` + - Test-only variables that override variables from the Wrangler configuration file. +- `secrets` + - Test-only secrets that override values loaded from `.dev.vars` and `.env` files. + +Each `WorkerInput` can also use `config` to provide an inline Wrangler configuration object: + + + +```ts +const server = createTestHarness({ + workers: [ + { + config: { + name: "api-worker", + main: "src/api.ts", + compatibility_date: "YYYY-MM-DD", + }, + }, + ], +}); +``` + + + +### Return type + +`createTestHarness()` returns a object with these methods: + +- `listen()` + - Starts the server and returns its current URL. Calling this more than once returns the same running server session until the server is closed or reset. +- `fetch(input, init)` + - Dispatches a fetch request through the server. Relative URLs are resolved against the current server URL. Absolute URLs are matched against the configured routes for each Worker and dispatched to the first matching Worker, or to the primary Worker if no routes match. +- `getWorker(name?)` + - Returns a handle for dispatching events directly to a Worker. When no name is provided, this returns the primary Worker. +- `getLogs()` + - Returns captured Workers runtime logs since the current server session started or `clearLogs()` was last called. +- `clearLogs()` + - Clears captured Workers runtime logs. +- `debug()` + - Prints a diagnostic timeline for this test server, including server events and captured Workers runtime logs. This is useful in a test runner failure or cleanup hook. +- `update(optionsOrUpdater)` + - Updates the server configuration with a `TestHarnessOptions` object or a function that receives the current options and returns the next options. If the server has not started yet, this configures the options used by `listen()`. If the server is running, this reloads the running Workers. Updating the number of Workers in a running server is not supported. +- `reset()` + - Restores the server to the options used when the current session first started. Storage is recreated, and the server URL may change after reset. +- `close()` + - Stops the server and releases all runtime resources. + +`getWorker(name?)` returns a object with these methods: + +- `fetch(input, init)` + - Dispatches a fetch event directly to this Worker. +- `scheduled(options)` '} /> + - Dispatches a scheduled event directly to this Worker. + +### Usage + +This example uses the Node.js built-in test runner: + + + +```ts +import assert from "node:assert/strict"; +import { after, afterEach, before, describe, test } from "node:test"; +import { createTestHarness } from "wrangler"; + +const server = createTestHarness({ + workers: [ + { configPath: "./wrangler.web.jsonc" }, + { configPath: "./wrangler.api.jsonc" }, + ], +}); + +const apiWorker = server.getWorker("api-worker"); + +describe("Worker", () => { + before(async () => { + await server.listen(); + }); + + afterEach(async () => { + await server.reset(); + }); + + after(async () => { + await server.close(); + }); + + test("dispatches through configured routes", async () => { + const response = await server.fetch("http://example.com/users/123"); + assert.equal(response.status, 200); + }); + + test("calls a specific Worker directly", async () => { + const response = await apiWorker.fetch("http://api.example.com/v1/users/123"); + assert.equal(response.status, 200); + }); + + test("triggers a scheduled handler", async () => { + const result = await apiWorker.scheduled({ + cron: "0 0 * * *", + scheduledTime: new Date(), + }); + assert.equal(result.outcome, "ok"); + }); +}); +``` + + ## `experimental_generateTypes` @@ -143,7 +308,13 @@ const result = await experimental_generateTypes({ ## `unstable_startWorker` -This API exposes the internals of Wrangler's dev server, and allows you to customise how it runs. For example, you could use `unstable_startWorker()` to run integration tests against your Worker. This example uses `node:test`, but should apply to any testing framework: +:::caution + +`unstable_startWorker()` is deprecated. Cloudflare now recommends [`createTestHarness()`](#createtestharness), which provides a harness specifically designed for integration testing. If you need to start a development server programmatically, use the Vite [`createServer()`](https://vite.dev/guide/api-javascript.html#createserver) API with the [Cloudflare Vite plugin](/workers/vite-plugin/). + +::: + +This API exposes the internals of the Wrangler dev server, and allows you to customize how it runs. This example uses `node:test`, but should apply to any testing framework: ```js import assert from "node:assert"; @@ -172,19 +343,17 @@ describe("worker", () => { ## `unstable_dev` -Start an HTTP server for testing your Worker. - -Once called, `unstable_dev` will return a `fetch()` function for invoking your Worker without needing to know the address or port, as well as a `stop()` function to shut down the HTTP server. +:::caution -By default, `unstable_dev` will perform integration tests against a local server. If you wish to perform an e2e test against a preview Worker, pass `local: false` in the `options` object when calling the `unstable_dev()` function. Note that e2e tests can be significantly slower than integration tests. +`unstable_dev()` is deprecated. For integration tests, Cloudflare now recommends [`createTestHarness()`](#createtestharness), which provides a harness specifically designed for integration testing. If you need to start a development server programmatically, use the Vite [`createServer()`](https://vite.dev/guide/api-javascript.html#createserver) API with the [Cloudflare Vite plugin](/workers/vite-plugin/). -:::note +::: -The `unstable_dev()` function has an `unstable_` prefix because the API is experimental and may change in the future. We recommend migrating to the `unstable_startWorker()` API, documented above. +Start an HTTP server for testing your Worker. -If you have been using `unstable_dev()` for integration testing and want to migrate to Cloudflare's Vitest integration, refer to the [Migrate from `unstable_dev` migration guide](/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev/) for more information. +Once called, `unstable_dev` will return a `fetch()` function for invoking your Worker without needing to know the address or port, as well as a `stop()` function to shut down the HTTP server. -::: +By default, `unstable_dev` will perform integration tests against a local server. If you wish to perform an e2e test against a preview Worker, pass `local: false` in the `options` object when calling the `unstable_dev()` function. Note that e2e tests can be significantly slower than integration tests. ### Constructor