astradevlabsastradevlabs
← All posts
Dev Tips5 min

Step-by-Step Playbook: Replace `unstable_dev` With Cloudflare's `createTestHarness()` Before Your Next Worker Release

Dev Tips

Cloudflare quietly shipped one of the more useful testing changes of the summer on July 27, 2026: Wrangler now exposes createTestHarness(), and Cloudflare explicitly recommends it over unstable_dev() and unstable_startWorker() for integration tests.

That matters because this is not just a renamed helper. The new harness is built around the thing teams actually care about before a release: exercising the Worker build that will ship, routing traffic across real configured Workers, and resetting storage cleanly between tests.

If your current test setup still boots a Worker from a source entrypoint and hopes that is close enough to production, this is a good week to fix it.

What changed on July 27

Cloudflare's changelog frames createTestHarness() as the new path for integration testing. The testing docs split the stack into two layers:

  • Use the Workers Vitest integration for unit tests and direct runtime assertions.
  • Use createTestHarness() for integration tests that need full HTTP routing, multiple Workers, Playwright, or outbound request mocking.

That split is the useful mental model. Unit tests still prove your functions. The harness proves your deployed shape.

Step 1: Point tests at Wrangler config, not source files

The first migration step is to stop thinking in terms of src/index.ts and start thinking in terms of configPath.

Cloudflare's starter pattern is minimal:

ts
import { createTestHarness } from "wrangler";

const server = createTestHarness({
  workers: [{ configPath: "./wrangler.jsonc" }],
});

That one change does two jobs for you. It loads the same bindings and routes your Worker expects, and it gives the harness enough context to test multiple Workers together later without rewriting the test model.

Step 2: Move lifecycle work into the test runner

The docs show the harness as a server you explicitly start, reset, and close. That is a better default than ad hoc setup inside each test.

A practical Vitest shape looks like this:

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

The key line is server.reset(). Cloudflare calls out storage reset as part of the harness contract, which means you no longer need fragile cleanup code for every test path.

Step 3: Keep external mocks in Node land

One of the best parts of the new harness is that it works with normal Node tooling. Cloudflare's examples use Mock Service Worker so outbound fetch() calls can be intercepted without custom Worker-only shims.

That gives you a cleaner test boundary:

  1. The harness owns Worker execution and route dispatch.
  2. MSW owns fake upstream APIs.
  3. Your assertions stay focused on application behavior.

If you already use Playwright, the same pattern scales. Cloudflare's integration examples show a fixture that starts the harness once, sets baseURL, and resets both MSW handlers and Worker storage after each test.

Step 4: For Vite projects, build first

This is the migration detail most teams will miss.

Cloudflare's configuration guide says Workers built with the Cloudflare Vite plugin should run vite build first, then point the harness at the generated Wrangler config inside dist. In other words, do not aim the harness at your dev server setup and assume you are testing the production artifact.

If your app has both a web Worker and an API Worker, the harness can run both in one local server and dispatch absolute URLs to the matching route. That makes cross-Worker integration coverage much closer to a release candidate than the older single-entrypoint pattern.

Step 5: Migrate off unstable_dev deliberately

Cloudflare also published a migration guide for teams coming from unstable_dev. The headline is simple: unstable_dev was a recommended integration path, but it is no longer the recommended one.

The fastest safe migration is:

  1. Leave your unit tests alone.
  2. Pick one integration suite that currently uses unstable_dev.
  3. Swap Worker bootstrapping to createTestHarness().
  4. Add server.reset() after each test.
  5. Move any network fakes to MSW if they are currently coupled to the old setup.
  6. For Vite-based Workers, build before the suite runs.

That sequence keeps the blast radius small while moving you onto the API Cloudflare is actively documenting.

Three mistakes to avoid this week

  • Do not pass only a source entry file when the point of the harness is to exercise configured Worker builds.
  • Do not skip reset logic, especially if you touch D1, KV, R2, or Durable Objects in tests.
  • Do not use the harness as a replacement for fast unit tests; Cloudflare still wants the Vitest integration for that layer.

Why this is worth doing now

This release is less about shiny new syntax and more about test honesty. The gap between "my Worker function returned the right value" and "my routed, configured, multi-Worker build behaves correctly" is where release-day bugs like to hide.

createTestHarness() narrows that gap with less custom glue than most teams already carry. If you maintain a Cloudflare Worker that talks to another Worker, hits an upstream API, or needs browser coverage, this is one of the cleaner upgrades you can make in August.

References