The answer. Running Worker code under Node with mocks tests a runtime you do not ship. The Vitest integration runs the tests themselves inside workerd, so Request, Response, streams, crypto and every binding behave exactly as in production, and each test file gets isolated storage. The package is @cloudflare/vitest-plugin (renamed from @cloudflare/vitest-pool-workers in August 2026): a Vite plugin, cloudflareTest(), that reads your Wrangler config. In tests, import { env, exports } from "cloudflare:workers"; exports.default.fetch(url) drives the whole Worker, env.DB or env.KV reach bindings directly. Work queued with ctx.waitUntil is invisible until you wait for it.
The pattern.
// vitest.config.ts
import { cloudflareTest, readD1Migrations } from "@cloudflare/vitest-plugin";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [cloudflareTest(async () => ({
wrangler: { configPath: "./wrangler.jsonc" },
miniflare: { bindings: { TEST_MIGRATIONS: await readD1Migrations("./migrations") } },
}))],
test: { setupFiles: ["./test/apply-migrations.ts"] },
});
// test/images.spec.ts
import { env, exports } from "cloudflare:workers";
import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
it("POST then GET", async () => {
const post = await exports.default.fetch("https://x/images", { method: "POST", body: JSON.stringify({ url: "https://a/b.png", author: "me" }) });
expect(post.status).toBe(201);
const rows = await env.DB.prepare("select count(*) as n from images").first<{ n: number }>();
expect(rows?.n).toBe(1);
});
Watch out.
- Any URL works for
fetch()in tests, but it must be absolute; the host is never resolved. - Storage isolation is per file. Tests inside one file share state, so a POST-then-GET pair depends on order.
- Outbound
fetch()to third parties is real unless you intercept it; use@msw/cloudflarerather than hand-rolled globals.
Related: d1-migrations-prepared-statements-and-batch · wrangler-dev-local-by-default-remote-bindings · n8n-code-nodes-compiled-with-parameter-hash-contract