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.
Point each entry in the workers array to the Wrangler configuration file for a project:
const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});For Workers built by the Cloudflare Vite plugin, run vite build first so tests use the production build output:
npx vite buildyarn vite buildpnpm vite buildThe generated Wrangler configuration works like any other configPath. Each Worker is configured independently, so one harness can run both project types:
const server = createTestHarness({
workers: [
// Wrangler project
{ configPath: "./workers/api/wrangler.jsonc" },
// Vite project (built output from the Cloudflare Vite plugin)
{ configPath: "./dist/web_worker/wrangler.json" },
],
});const server = createTestHarness({
workers: [
// Wrangler project
{ configPath: "./workers/api/wrangler.jsonc" },
// Vite project (built output from the Cloudflare Vite plugin)
{ configPath: "./dist/web_worker/wrangler.json" },
],
});By default, the test harness loads the top-level Wrangler configuration. Set env if you want to load a specific environment from the configuration.
const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc", env: "test" }],
});const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc", env: "test" }],
});You can override vars and secrets for each Worker in the harness if you want to avoid creating a separate Wrangler environment for testing.
const server = createTestHarness({
workers: [
{
configPath: "./wrangler.jsonc",
vars: { API_HOST: "http://identity.example.com" },
secrets: { API_TOKEN: "test-token" },
},
],
});const server = createTestHarness({
workers: [
{
configPath: "./wrangler.jsonc",
vars: { API_HOST: "http://identity.example.com" },
secrets: { API_TOKEN: "test-token" },
},
],
});If part of the Worker configuration depends on the test setup, you can call createTestHarness() without options and configure the harness with server.update() before starting the server.
const server = createTestHarness();
let upstream;
beforeAll(async () => {
upstream = await startLocalApi();
await server.update({
workers: [
{
configPath: "./wrangler.jsonc",
vars: { API_HOST: upstream.url },
},
],
});
await server.listen();
});
afterAll(async () => {
await server.close();
await upstream.close();
});const server = createTestHarness();
let upstream: { url: string; close(): Promise<void> };
beforeAll(async () => {
upstream = await startLocalApi();
await server.update({
workers: [
{
configPath: "./wrangler.jsonc",
vars: { API_HOST: upstream.url },
},
],
});
await server.listen();
});
afterAll(async () => {
await server.close();
await upstream.close();
});When reusing a server across tests, call server.reset() after each test. It recreates local storage and restores Workers to the options used when the current session started.
const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});
afterEach(async () => {
await server.reset();
});const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});
afterEach(async () => {
await server.reset();
});After a reset, apply any required schema migrations and seed data again. For examples, refer to Prepare test state.
server.debug() prints the server timeline and captured Workers runtime logs. Call it when a test throws an exception or fails and you need more information to debug it.
The following example uses a cleanup hook from Vitest:
const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});
afterEach(({ task }) => {
if (task.result?.state === "fail") {
server.debug();
}
});const server = createTestHarness({
workers: [{ configPath: "./wrangler.jsonc" }],
});
afterEach(({ task }) => {
if (task.result?.state === "fail") {
server.debug();
}
});server.getWorker() accepts types for the Worker environment and module exports. You can define these types manually. But to keep them aligned with your Worker, you can generate the env type from the Wrangler configuration and derive the exports from its source module.
Give each Worker a distinct environment interface so the generated declarations can be used together:
npx wrangler types ./workers/api/worker-configuration.d.ts --config ./workers/api/wrangler.jsonc --env-interface ApiEnvyarn wrangler types ./workers/api/worker-configuration.d.ts --config ./workers/api/wrangler.jsonc --env-interface ApiEnvpnpm wrangler types ./workers/api/worker-configuration.d.ts --config ./workers/api/wrangler.jsonc --env-interface ApiEnvRepeat this command for each Worker and include the generated files in the TypeScript configuration for your tests:
{
"include": ["./workers/*/worker-configuration.d.ts", "./tests/**/*.ts"]
}Pass the generated environment interface to server.getWorker(). Use typeof import() to derive the Worker exports from its source module:
const apiWorker = server.getWorker("api-worker");const apiWorker = server.getWorker<
ApiEnv,
typeof import("../workers/api/index")
>("api-worker");In this example, ApiEnv comes from worker-configuration.d.ts. The module type includes the default export and its RPC methods. Re-run wrangler types when the Worker configuration changes.