Sign inConnect your agent
infochanged

Workers - Run integration tests against your Worker's production build

Announced
Tags
Workers
Severity
info, set by reef/rss-rule-v1
Source
https://developers.cloudflare.com/changelog/post/2026-07-21-integration-test-harness/

<p>Wrangler now provides <code>createTestHarness()</code>, an API for running integration tests against Workers built with <a href="https://developers.cloudflare.com/workers/testing/test-harness/configure/#configure-worker-projects">Wrangler or the Cloudflare Vite plugin</a> from any Node.js test runner.</p> <p>The test harness starts a local Worker server with <a href="https://developers.cloudflare.com/workers/wrangler/api/#createtestharness">helpers for dispatching requests, resetting storage, and inspecting runtime logs</a>.</p> <p>This is useful for tests that need to:</p> <ul> <li><a href="https://developers.cloudflare.com/workers/testing/test-harness/interact-with-workers/#test-route-dispatch-across-workers">Route requests across multiple Workers</a></li> <li><a href="https://developers.cloudflare.com/workers/testing/test-harness/integrations/#mock-service-worker">Mock outbound <code>fetch()</code> requests</a> with Node.js request mocking libraries such as <a href="https://mswjs.io/" target="_blank" rel="noopener">MSW<span class="external-link"> ↗︎</span></a></li> <li><a href="https://developers.cloudflare.com/workers/testing/test-harness/integrations/#playwright">Run Playwright tests against a Worker</a></li> </ul> <p>For example, this test starts two Workers and mocks an upstream API:</p> <div><div data-nb-tabs data-nb-sync-key="workersExamples" class><div class="relative flex border-b border-border" role="tablist" data-nb-tabs-list><span class="bg-primary pointer-events-none absolute -bottom-px h-0.5 rounded-t-sm transition-[left,width] duration-200 ease-out" data-nb-tabs-indicator aria-hidden="true"></span></div><div class="mt-3"><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="JavaScript" class><figure class="nb-code-figure nb-code-figure-titled" data-nb-lang="js"><figcaption class="nb-code-title"><span class="nb-code-title-name">tests/vitest.test.js</span><span class="nb-code-title-lang">js</span></figcaption><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="js" data-nb-lang="js"><code><span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { afterAll, afterEach, beforeAll, test } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "vitest"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { http, HttpResponse } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "msw"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { setupServer } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "msw/node"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { createTestHarness } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "wrangler"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> network</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> setupServer</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> server</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> createTestHarness</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> workers: [</span></span> <span class="line"><span class="nb-shiki-21nrsd"> /** Includes "routes": ["example.com/*"] */</span></span> <span class="line"><span class="nb-shiki-140thh"> { configPath: </span><span class="nb-shiki-mdbnqw">"./workers/web/wrangler.jsonc"</span><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-21nrsd"> /** Includes "routes": ["api.example.com/v1/*"] */</span></span> <span class="line"><span class="nb-shiki-140thh"> { configPath: </span><span class="nb-shiki-mdbnqw">"./workers/api/wrangler.jsonc"</span><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> ],</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">beforeAll</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">listen</span><span class="nb-shiki-140thh">({ onUnhandledRequest: </span><span class="nb-shiki-mdbnqw">"error"</span><span class="nb-shiki-140thh"> });</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">listen</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">afterEach</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">resetHandlers</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">reset</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">afterAll</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">close</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">close</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">test</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"routes requests to each Worker"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> ({ </span><span class="nb-shiki-1jdh33">expect</span><span class="nb-shiki-140thh"> }) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-21nrsd"> // Mock the outbound fetch used to load user profiles.</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">use</span><span class="nb-shiki-140thh">(</span></span> <span class="line"><span class="nb-shiki-140thh"> http.</span><span class="nb-shiki-1t8gfj">get</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"http://identity.example.com/profile/123"</span><span class="nb-shiki-140thh">, ({ </span><span class="nb-shiki-1jdh33">params</span><span class="nb-shiki-140thh"> }) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> HttpResponse.</span><span class="nb-shiki-1t8gfj">json</span><span class="nb-shiki-140thh">({ id: </span><span class="nb-shiki-dzsirb">123</span><span class="nb-shiki-140thh">, name: </span><span class="nb-shiki-mdbnqw">"Ada"</span><span class="nb-shiki-140thh"> });</span></span> <span class="line"><span class="nb-shiki-140thh"> }),</span></span> <span class="line"><span class="nb-shiki-140thh"> );</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> apiWorkerResponse</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "http://api.example.com/v1/users/123"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> );</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> expect</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-140thh"> apiWorkerResponse.</span><span class="nb-shiki-1t8gfj">json</span><span class="nb-shiki-140thh">()).</span><span class="nb-shiki-1t8gfj">toEqual</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> id: </span><span class="nb-shiki-dzsirb">123</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> name: </span><span class="nb-shiki-mdbnqw">"Ada"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> });</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> webWorkerResponse</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"http://example.com/users/123"</span><span class="nb-shiki-140thh">);</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> expect</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-140thh"> webWorkerResponse.</span><span class="nb-shiki-1t8gfj">text</span><span class="nb-shiki-140thh">()).</span><span class="nb-shiki-1t8gfj">toBe</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"Profile: Ada"</span><span class="nb-shiki-140thh">);</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span></code></pre></figure></div><div role="tabpanel" data-nb-tabs-content data-nb-tab-label="TypeScript" class><figure class="nb-code-figure nb-code-figure-titled" data-nb-lang="ts"><figcaption class="nb-code-title"><span class="nb-code-title-name">tests/vitest.test.ts</span><span class="nb-code-title-lang">ts</span></figcaption><pre class="astro-code astro-code-themes github-light github-dark nb-shiki-c6xiwz" tabindex="0" data-language="ts" data-nb-lang="ts"><code><span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { afterAll, afterEach, beforeAll, test } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "vitest"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { http, HttpResponse } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "msw"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { setupServer } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "msw/node"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { createTestHarness } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "wrangler"</span><span class="nb-shiki-140thh">;</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> network</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> setupServer</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> server</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> createTestHarness</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> workers: [</span></span> <span class="line"><span class="nb-shiki-21nrsd"> /** Includes "routes": ["example.com/*"] */</span></span> <span class="line"><span class="nb-shiki-140thh"> { configPath: </span><span class="nb-shiki-mdbnqw">"./workers/web/wrangler.jsonc"</span><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-21nrsd"> /** Includes "routes": ["api.example.com/v1/*"] */</span></span> <span class="line"><span class="nb-shiki-140thh"> { configPath: </span><span class="nb-shiki-mdbnqw">"./workers/api/wrangler.jsonc"</span><span class="nb-shiki-140thh"> },</span></span> <span class="line"><span class="nb-shiki-140thh"> ],</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">beforeAll</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">listen</span><span class="nb-shiki-140thh">({ onUnhandledRequest: </span><span class="nb-shiki-mdbnqw">"error"</span><span class="nb-shiki-140thh"> });</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">listen</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">afterEach</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">resetHandlers</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">reset</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">afterAll</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">close</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">close</span><span class="nb-shiki-140thh">();</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1t8gfj">test</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"routes requests to each Worker"</span><span class="nb-shiki-140thh">, </span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> ({ </span><span class="nb-shiki-1jdh33">expect</span><span class="nb-shiki-140thh"> }) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-21nrsd"> // Mock the outbound fetch used to load user profiles.</span></span> <span class="line"><span class="nb-shiki-140thh"> network.</span><span class="nb-shiki-1t8gfj">use</span><span class="nb-shiki-140thh">(</span></span> <span class="line"><span class="nb-shiki-140thh"> http.</span><span class="nb-shiki-1t8gfj">get</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"http://identity.example.com/profile/123"</span><span class="nb-shiki-140thh">, ({ </span><span class="nb-shiki-1jdh33">params</span><span class="nb-shiki-140thh"> }) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span> <span class="line"><span class="nb-shiki-1itgoe"> return</span><span class="nb-shiki-140thh"> HttpResponse.</span><span class="nb-shiki-1t8gfj">json</span><span class="nb-shiki-140thh">({ id: </span><span class="nb-shiki-dzsirb">123</span><span class="nb-shiki-140thh">, name: </span><span class="nb-shiki-mdbnqw">"Ada"</span><span class="nb-shiki-140thh"> });</span></span> <span class="line"><span class="nb-shiki-140thh"> }),</span></span> <span class="line"><span class="nb-shiki-140thh"> );</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> apiWorkerResponse</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(</span></span> <span class="line"><span class="nb-shiki-mdbnqw"> "http://api.example.com/v1/users/123"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> );</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> expect</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-140thh"> apiWorkerResponse.</span><span class="nb-shiki-1t8gfj">json</span><span class="nb-shiki-140thh">()).</span><span class="nb-shiki-1t8gfj">toEqual</span><span class="nb-shiki-140thh">({</span></span> <span class="line"><span class="nb-shiki-140thh"> id: </span><span class="nb-shiki-dzsirb">123</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> name: </span><span class="nb-shiki-mdbnqw">"Ada"</span><span class="nb-shiki-140thh">,</span></span> <span class="line"><span class="nb-shiki-140thh"> });</span></span> <span class="line"></span> <span class="line"><span class="nb-shiki-1itgoe"> const</span><span class="nb-shiki-dzsirb"> webWorkerResponse</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-140thh"> server.</span><span class="nb-shiki-1t8gfj">fetch</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"http://example.com/users/123"</span><span class="nb-shiki-140thh">);</span></span> <span class="line"><span class="nb-shiki-1t8gfj"> expect</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">await</span><span class="nb-shiki-140thh"> webWorkerResponse.</span><span class="nb-shiki-1t8gfj">text</span><span class="nb-shiki-140thh">()).</span><span class="nb-shiki-1t8gfj">toBe</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"Profile: Ada"</span><span class="nb-shiki-140thh">);</span></span> <span class="line"><span class="nb-shiki-140thh">});</span></span></code></pre></figure></div></div></div><script type="module" src="https://developers.cloudflare.com/home/runner/work/cloudflare-docs/cloudflare-docs/src/components/ui/tabs/Tabs.astro?astro&type=script&index=0&lang.ts"></script></div> <p>Cloudflare now recommends <code>createTestHarness()</code> for integration tests instead of <a href="https://developers.cloudflare.com/workers/testing/unstable_startworker/"><code>unstable_startWorker()</code></a> or <a href="https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev"><code>unstable_dev()</code></a>. To start a development server programmatically, use the Vite <a href="https://vite.dev/guide/api-javascript.html#createserver" target="_blank" rel="noopener"><code>createServer()</code><span class="external-link"> ↗︎</span></a> API with the <a href="https://developers.cloudflare.com/workers/vite-plugin/">Cloudflare Vite plugin</a>.</p> <p>For more information about <code>createTestHarness()</code>, refer to the <a href="https://developers.cloudflare.com/workers/testing/test-harness/">Integration test harness guide</a>.</p>

The text above is the source's. reef added the severity, the dates it could read from the text, and the endpoints and scopes named in code.