Use when Cloudflare Workers code needs tests locally: Miniflare 3 (workerd-backed), wrangler dev, emulated KV, Durable Objects, R2 and D1 bindings, and in-process tests with Vitest and @cloudflare/vitest-pool-workers.
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Miniflare 3 is the official Workers simulator. Per
developers.cloudflare.com/workers,
Miniflare 3 is built on top of workerd - the same runtime that
serves production Workers traffic. This means local-mode
behaviour matches prod with very high fidelity.
Three integration patterns:
wrangler dev (CLI; live-reload during dev).getMiniflare(); for integration
tests).@cloudflare/vitest-pool-workers (Vitest + in-process
Workers; the recommended unit-test path).npm install --save-dev wrangler miniflare @cloudflare/vitest-pool-workers vitestwrangler dev --local # workerd locally; no remote calls
wrangler dev # default = local-mode in Wrangler 3+Now curl http://localhost:8787 hits the local Worker.
Per miniflare.dev:
import { Miniflare } from 'miniflare';
const mf = new Miniflare({
scriptPath: './src/worker.js',
modules: true,
kvNamespaces: ['MY_KV'],
d1Databases: ['DB'],
r2Buckets: ['MY_BUCKET'],
durableObjects: {
COUNTER: 'Counter',
},
});
const res = await mf.dispatchFetch('https://example.com/');
expect(res.status).toBe(200);
expect(await res.text()).toBe('Hello');
await mf.dispose();Per developers.cloudflare.com/workers/testing/vitest-integration:
// vitest.config.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});
// src/worker.test.ts
import { env, SELF } from 'cloudflare:test';
import { expect, test } from 'vitest';
test('responds with hello', async () => {
const response = await SELF.fetch('https://example.com/');
expect(await response.text()).toBe('Hello');
});
test('writes to KV', async () => {
await env.MY_KV.put('key', 'value');
expect(await env.MY_KV.get('key')).toBe('value');
});This is the lowest-overhead test path - runs inside workerd.
import { env } from 'cloudflare:test';
test('counter increments', async () => {
const id = env.COUNTER.idFromName('test');
const stub = env.COUNTER.get(id);
const r1 = await stub.fetch('https://example.com/increment');
expect(await r1.text()).toBe('1');
const r2 = await stub.fetch('https://example.com/increment');
expect(await r2.text()).toBe('2');
});Per Cloudflare docs, the vitest-pool-workers env exposes the
Durable Object namespace exactly as in production.
import { env } from 'cloudflare:test';
test('d1 query', async () => {
await env.DB.exec('CREATE TABLE IF NOT EXISTS users (id INT, name TEXT)');
await env.DB.prepare('INSERT INTO users VALUES (?, ?)').bind(1, 'alice').run();
const { results } = await env.DB.prepare('SELECT * FROM users').all();
expect(results).toEqual([{ id: 1, name: 'alice' }]);
});npx vitest run # @cloudflare/vitest-pool-workers
wrangler dev # CLI devjobs:
workers-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx vitest runNo CF account/API key needed - vitest-pool-workers runs
locally via workerd.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Test against deployed Worker | Slow; flaky; rate-limited | Use vitest-pool-workers |
| Mock the fetch API | Loses Workers' standard-Web-Platform shape | Use real Worker isolation |
| Skip Durable Object tests | Fan-out + consistency bugs hide | Test DO classes directly |
KV getWithMetadata not tested for stale-read behavior | KV is eventually consistent | Test the consistency requirements |
| Local-mode time differs from prod | Cron / scheduled triggers won't fire | Test via deployed Worker + Cloudflare Cron Trigger |
| Hardcoded API key in worker | Exposed in source map / fetch | Use Workers Secrets (wrangler secret) |
| No assertion on response headers | Cache-Control / CORS bugs | Inspect response.headers |
| Cold-start tests against local Worker | Local cold start ~0ms; not representative | Per serverless-integration-test-builder (its cold-start budget reference), test cold-start budget on deployed Worker |
remote: true config in
Wrangler 3.dispatchFetch('https://example.com/__scheduled').serverless-integration-test-builder (its cold-start budget reference).aws-sam-local-testing,
azure-functions-tests.