Skip to content

Integration testing

Integration tests test multiple units of your Worker together by sending HTTP requests to your Worker and asserting on the HTTP responses. As an example, consider the following Worker:

export function add(a, b) {
return a + b;
}
export default {
async fetch(request) {
const url = new URL(request.url);
const a = parseInt(url.searchParams.get("a"));
const b = parseInt(url.searchParams.get("b"));
return new Response(add(a, b));
}
}

An integration test for this Worker might look like the following example:

// Start Worker HTTP server on port 8787 running `index.mjs` then...
const response = await fetch("http://localhost:8787/?a=1&b=2");
assert((await response.text()) === "3");

In the above example, instead of importing the add function as a unit test would do, you make a direct call to the endpoint, testing that the Worker responds at the endpoint with the appropriate response.

Vitest integration

The recommended way to write integration tests for your Workers is by using the Workers Vitest integration. Vitest can be configured to run integrations against a single Worker or multiple Workers.

Testing via SELF

If testing a single Worker, you can use the SELF fetcher provided by the cloudflare:test API.

import { SELF } from "cloudflare:test";
it("dispatches fetch event", async () => {
const response = await SELF.fetch("https://example.com");
expect(await response.text()).toMatchInlineSnapshot(...);
});

When using SELF 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 also means your Worker uses the same subtly different module resolution behavior provided by Vite.

Usually this is not a problem, but if you would like to run your Worker in a fresh environment that is as close to production as possible, using an auxiliary Worker may be a good idea. Auxiliary Workers have some developer experience (DX) limitations.

Testing via auxiliary Workers

It is also possible to configure Workers for integration testing via vitest.config.ts. An example vitest.config.ts configuration file on GitHub.

The Worker can then be referenced like the following example:

import { env } from "cloudflare:test";
import { expect, it } from "vitest";
it("dispatches fetch event", async () => {
const response = await env.WORKER.fetch("http://example.com");
expect(await response.text()).toBe("👋");
});

Instead of running the Worker-under-test in the same Worker as the test runner like SELF, this example defines the Worker-under-test as an auxiliary Worker. This means the Worker runs in a separate isolate to the test runner, with a different global scope. The Worker-under-test runs in an environment closer to production, but Vite transformations and hot-module-reloading aren’t applied to the Worker—you must compile your TypeScript to JavaScript beforehand.

Auxiliary Workers cannot be configured from wrangler.toml files. You must use Miniflare WorkerOptions in vitest.config.ts.

Wrangler’s unstable_dev() API

If you do not want to use Vitest and would like to write integration tests for a single Worker, consider using Wrangler’s unstable_dev() API. unstable_dev() allows you to start an HTTP server similar to wrangler dev that you can send HTTP requests to. unstable_dev() will automatically load options from your Wrangler configuration file. Note that unstable_dev() is an experimental API subject to breaking changes.

import assert from "node:assert";
import { unstable_dev } from "wrangler";
const worker = await unstable_dev("./index.mjs");
try {
const response = await worker.fetch("/?a=1&b=2");
assert.strictEqual(await response.text(), "3");
} finally {
await worker.stop();
}

Miniflare’s API

If you would like to write integration tests for multiple Workers, need direct access to bindings outside your Worker in tests, or have another advanced use case, consider using Miniflare’s API directly. Miniflare is the foundation for the other testing tools on this page, exposing a JavaScript API for the workerd runtime and local simulators for the other Developer Platform products. Unlike unstable_dev(), Miniflare does not automatically load options from your Wrangler configuration file.

import assert from "node:assert";
import { Miniflare } from "miniflare";
const mf = new Miniflare({
modules: true,
scriptPath: "./index.mjs",
});
try {
const response = await mf.dispatchFetch("http://example.com/?a=1&b=2");
assert.strictEqual(await response.text(), "3");
} finally {
await mf.dispose();
}
  • Recipes - Example integration tests for Workers using the Workers Vitest integration.