Skip to main content Home Creators amo-tech-ai lumina-studio cloudflare-workers-testing
cloudflare-workers-testing Comprehensive testing guide for Cloudflare Workers using Vitest and @cloudflare/vitest-pool-workers. Use for test setup, binding mocks (D1/KV/R2/DO), integration tests, or encountering test failures, mock errors, coverage issues.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/amo-tech-ai/lumina-studio --skill cloudflare-workers-testingThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository
Fashion shoot-production toolkit — the real-shoot pipeline from creative direction to delivery, consolidated into one skill with phase-organized references. Use for: creative direction / concept / moodboard rationale; shot lists (brief → shot-by-shot table with timing/crew/setup groups/channels); crew call sheets; model direction / casting / pose cards; garment lifestyle & location shoot briefs (scouting, props, talent); product, macro, Amazon, and Shopify product-photography specs (shot types, camera, lighting, QC); lighting plans (key/fill/accent per shot type & brand DNA); live shoot-day coordination (shot counter, schedule, issue log, reshuffling); post-shoot asset triage (auto-sort by shot/setup/channel, roughs vs finals, EXIF match); and AI ad generation via ComfyDeploy Morpheus. Triggers on shoot, shot list, call sheet, lighting plan, retouch/QC brief, lookbook, editorial, product shoot, reshoot, or any fashion production-planning task.
Consolidated Linear hub for linear.app issue/project/cycle/initiative/milestone work, Linear Method planning, issue generation, search, sync, and iPix IPI-### workflows. Use whenever the user mentions Linear, linear.app, IPI-###, PLT-###, COM-###, UI-###, AI-###, DNA-###, issue, project, cycle, roadmap, milestone, backlog, or asks to create/update/search/sync Linear work. Prefer this skill over legacy linear-* skills.
Research-before-code for iPix. Use whenever implementing, architecting, debugging, writing migrations, adding vendors/tools, or starting an IPI task — even if the user did not say "research". Also use for /research, "what's the best way", "should we build or reuse", platform-first checks, and before any custom Cloudflare/Supabase/ Mastra/CopilotKit/Stripe work. Produces a concise evidence-backed research report and blocks coding until a recommendation exists (except trivial ≤3-file known-pattern edits). Prefer official docs, dashboards, CLI, SDK, MCP, and existing iPix code over new custom code. NOT for pure copy edits, or when the user explicitly says "skip research and implement this known one-liner".
Related occupations SOC
Based on SOC occupation classification
name cloudflare-workers-testing description Comprehensive testing guide for Cloudflare Workers using Vitest and @cloudflare/vitest-pool-workers. Use for test setup, binding mocks (D1/KV/R2/DO), integration tests, or encountering test failures, mock errors, coverage issues. license MIT metadata {"keywords":"cloudflare-workers, workers-testing, vitest, vitest-workers, miniflare, cloudflare-test, unit-testing, integration-testing, binding-mocks, d1-testing, kv-testing, r2-testing, durable-objects-testing, queue-testing, workers-ai-testing, test-coverage, test-failures, mock-errors, @cloudflare/vitest-pool-workers, cloudflare:test, env-mocking, execution-context, workers-test-setup, vitest-config, test-driven-development, tdd-workers","version":"1.0.0","last_verified":"2025-01-27","production_tested":true,"token_savings":"~70%","errors_prevented":8,"templates_included":3,"references_included":5,"scripts_included":2,"vitest_version":"2.1.8","workers_types_version":"4.20251125.0","vitest_pool_workers_version":"0.7.2"}
Cloudflare Workers Testing with Vitest
Status : ✅ Production Ready | Last Verified: 2025-01-27
Vitest : 2.1.8 | @cloudflare/vitest-pool-workers : 0.7.2 | Miniflare : Latest
Table of Contents
What Is Workers Testing? Testing Cloudflare Workers with Vitest and @cloudflare/vitest-pool-workers enables writing unit and integration tests that run in a real Workers environment with full binding support (D1, KV, R2, Durable Objects, Queues, AI). Tests execute in Miniflare for local development and can run in CI/CD with actual Workers runtime behavior.
Key capabilities : Binding mocks, execution context testing, edge runtime simulation, coverage tracking, fast test execution.
New in 2025 @cloudflare/vitest-pool-workers 0.7.2 (January 2025):
BREAKING : Miniflare v3 → requires Node.js 18+
NEW : cloudflare:test module for env/ctx access
IMPROVED : Faster isolated storage for bindings
FIXED : Worker-to-worker service bindings now work correctly
ADDED : Support for Vectorize and Workers AI bindings
Migration from older versions :
bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2
export default defineWorkersConfig({
test : {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
miniflare: { compatibilityDate: '2025-01-27' }
}
}
}
});
Quick Start (5 Minutes)
1. Install Dependencies bun add -D vitest @cloudflare/vitest-pool-workers
2. Create vitest.config.ts import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config' ;
export default defineWorkersConfig ({
test : {
poolOptions : {
workers : {
wrangler : { configPath : './wrangler.jsonc' },
miniflare : {
compatibilityDate : '2025-01-27' ,
compatibilityFlags : ['nodejs_compat' ]
}
}
}
}
});
3. Write Your First Test import { describe, it, expect } from 'vitest' ;
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test' ;
import worker from '../src/index' ;
describe ('Worker' , () => {
it ('responds with 200' , async () => {
const request = new Request ('http://example.com/' );
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (200 );
});
});
4. Run Tests
Critical Rules
1. Always Use cloudflare:test for Env Access import { env } from 'cloudflare:test' ;
it ('queries D1' , async () => {
const result = await env.DB .prepare ('SELECT * FROM users' ).all ();
expect (result.results ).toHaveLength (0 );
});
const env = { DB : mockDB };
Why : cloudflare:test provides real bindings configured from wrangler.jsonc with isolated storage per test.
2. Always Wait on Execution Context it ('handles async operations' , async () => {
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (200 );
});
it ('missing wait' , async () => {
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
expect (response.status ).toBe (200 );
});
Why : Workers use ctx.waitUntil() for background tasks (logging, analytics). Without waiting, these tasks may not complete in tests.
3. Each Test Gets Isolated Storage describe ('KV Operations' , () => {
it ('test 1: writes to KV' , async () => {
await env.CACHE .put ('key' , 'value1' );
const val = await env.CACHE .get ('key' );
expect (val).toBe ('value1' );
});
it ('test 2: clean state' , async () => {
const val = await env.CACHE .get ('key' );
expect (val).toBeNull ();
});
});
Why : Each test runs with fresh binding storage (automatic isolation).
4. Use Wrangler Config for Bindings
export default defineWorkersConfig ({
test : {
poolOptions : {
workers : {
wrangler : { configPath : './wrangler.jsonc' }
}
}
}
});
export default defineWorkersConfig ({
test : {
poolOptions : {
workers : {
miniflare : { compatibilityDate : '2025-01-27' }
}
}
}
});
Why : Wrangler config defines all bindings (D1, KV, R2, etc.). Without it, env will be empty.
5. Match Compatibility Date
miniflare : {
compatibilityDate : '2025-01-27'
}
{
"compatibility_date" : "2025-01-27"
}
Why : Ensures test environment matches production runtime behavior.
Core Concepts
Binding Testing Patterns import { env } from 'cloudflare:test' ;
it ('queries D1' , async () => {
await env.DB .prepare ('INSERT INTO users (name) VALUES (?)' ).bind ('Alice' ).run ();
const result = await env.DB .prepare ('SELECT * FROM users WHERE name = ?' ).bind ('Alice' ).first ();
expect (result?.name ).toBe ('Alice' );
});
it ('reads from KV' , async () => {
await env.CACHE .put ('test-key' , 'test-value' );
const value = await env.CACHE .get ('test-key' );
expect (value).toBe ('test-value' );
});
it ('uploads to R2' , async () => {
await env.BUCKET .put ('file.txt' , 'Hello World' );
const object = await env.BUCKET .get ('file.txt' );
expect (await object ?.text ()).toBe ('Hello World' );
});
it ('interacts with Durable Object' , async () => {
const id = env.COUNTER .idFromName ('test-counter' );
const stub = env.COUNTER .get (id);
const response = await stub.fetch ('http://fake/increment' );
const data = await response.json ();
expect (data.count ).toBe (1 );
});
Unit vs Integration Tests Unit Test (single function):
import { validateInput } from '../src/utils/validator' ;
it ('validates input' , () => {
const result = validateInput ({ name : 'Alice' , age : 30 });
expect (result.valid ).toBe (true );
});
Integration Test (full fetch handler):
import worker from '../src/index' ;
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test' ;
it ('handles full request flow' , async () => {
const request = new Request ('http://example.com/api/users' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify ({ name : 'Alice' })
});
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (201 );
const user = await response.json ();
expect (user.name ).toBe ('Alice' );
});
Coverage Configuration export default defineWorkersConfig ({
test : {
coverage : {
provider : 'v8' ,
reporter : ['text' , 'json' , 'html' ],
include : ['src/**/*.ts' ],
exclude : ['src/**/*.test.ts' , 'src/**/*.spec.ts' ],
thresholds : {
lines : 80 ,
functions : 80 ,
branches : 80 ,
statements : 80
}
}
}
});
bunx vitest run --coverage
Top 5 Use Cases
1. Testing API Endpoints with D1 it ('creates user via API' , async () => {
const request = new Request ('http://example.com/api/users' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify ({ name : 'Bob' , email : 'bob@example.com' })
});
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (201 );
const user = await env.DB .prepare ('SELECT * FROM users WHERE email = ?' )
.bind ('bob@example.com' )
.first ();
expect (user?.name ).toBe ('Bob' );
});
2. Testing Caching with KV it ('caches API responses' , async () => {
const req1 = new Request ('http://example.com/api/data' );
const ctx1 = createExecutionContext ();
const res1 = await worker.fetch (req1, env, ctx1);
await waitOnExecutionContext (ctx1);
expect (res1.headers .get ('X-Cache' )).toBe ('MISS' );
const req2 = new Request ('http://example.com/api/data' );
const ctx2 = createExecutionContext ();
const res2 = await worker.fetch (req2, env, ctx2);
await waitOnExecutionContext (ctx2);
expect (res2.headers .get ('X-Cache' )).toBe ('HIT' );
});
3. Testing File Uploads to R2 it ('handles file upload' , async () => {
const formData = new FormData ();
formData.append ('file' , new Blob (['test content' ], { type : 'text/plain' }), 'test.txt' );
const request = new Request ('http://example.com/upload' , {
method : 'POST' ,
body : formData
});
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (200 );
const object = await env.BUCKET .get ('test.txt' );
expect (await object ?.text ()).toBe ('test content' );
});
4. Testing Durable Objects State it ('maintains counter state' , async () => {
const id = env.COUNTER .idFromName ('my-counter' );
const stub = env.COUNTER .get (id);
for (let i = 0 ; i < 3 ; i++) {
await stub.fetch ('http://fake/increment' );
}
const response = await stub.fetch ('http://fake/value' );
const data = await response.json ();
expect (data.count ).toBe (3 );
});
5. Testing Queue Consumers it ('processes queue messages' , async () => {
const messages = [
{ id : '1' , body : { action : 'email' , to : 'user@example.com' }, timestamp : new Date () }
];
await worker.queue (
{
queue : 'my-queue' ,
messages,
retryAll : () => {},
ackAll : () => {}
},
env
);
const log = await env.DB .prepare ('SELECT * FROM email_log WHERE id = ?' ).bind ('1' ).first ();
expect (log?.status ).toBe ('sent' );
});
Best Practices
✅ DO
Use descriptive test names :
it ('returns 404 when user not found' , async () => {});
it ('validates email format before saving' , async () => {});
Test error cases :
it ('returns 400 for invalid JSON' , async () => {
const request = new Request ('http://example.com/api' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : 'invalid json'
});
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (400 );
});
Group related tests :
describe ('User API' , () => {
describe ('POST /users' , () => {
it ('creates user with valid data' , async () => {});
it ('rejects duplicate email' , async () => {});
it ('validates required fields' , async () => {});
});
});
Use beforeEach for setup :
describe ('Database tests' , () => {
beforeEach (async () => {
await env.DB .prepare ('INSERT INTO users (name) VALUES (?)' ).bind ('Test User' ).run ();
});
it ('queries users' , async () => {
const result = await env.DB .prepare ('SELECT * FROM users' ).all ();
expect (result.results ).toHaveLength (1 );
});
});
Test realistic scenarios :
it ('handles concurrent requests' , async () => {
const requests = Array (10 ).fill (null ).map (() =>
worker.fetch (new Request ('http://example.com/' ), env, createExecutionContext ())
);
const responses = await Promise .all (requests);
expect (responses.every (r => r.status === 200 )).toBe (true );
});
❌ DON'T
Don't share state between tests :
let counter = 0 ;
it ('test 1' , () => { counter++; });
it ('test 2' , () => { expect (counter).toBe (1 ); });
it ('test 1' , () => { const counter = 0 ; counter++; });
it ('test 2' , () => { const counter = 0 ; });
Don't forget to wait :
const response = await worker.fetch (request, env, ctx);
expect (response.status ).toBe (200 );
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
expect (response.status ).toBe (200 );
Don't hardcode URLs :
const request = new Request ('http://example.com/test' );
const request = new Request ('http://fake-host/test' );
Don't test implementation details :
expect (worker.privateHelperFunction ).toBeDefined ();
const response = await worker.fetch (request, env, ctx);
expect (response.status ).toBe (200 );
Top 8 Errors Prevented
1. ❌ ReferenceError: env is not defined Cause : Not importing env from cloudflare:test.
import { env } from 'cloudflare:test' ;
Prevention : Always use cloudflare:test module for env access.
2. ❌ TypeError: Cannot read property 'DB' of undefined Cause : wrangler.jsonc not loaded in vitest.config.ts.
export default defineWorkersConfig ({
test : {
poolOptions : {
workers : {
wrangler : { configPath : './wrangler.jsonc' }
}
}
}
});
Prevention : Always configure wrangler path in vitest config.
3. ❌ Error: D1_ERROR: no such table: users Cause : D1 database schema not applied in tests.
beforeEach (async () => {
await env.DB .exec (`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
` );
});
beforeEach (async () => {
const schema = await fs.readFile ('./migrations/schema.sql' , 'utf-8' );
await env.DB .exec (schema);
});
Prevention : Create schema before each test or use shared setup.
4. ❌ Error: ctx.waitUntil tasks did not complete Cause : Missing await waitOnExecutionContext(ctx).
const ctx = createExecutionContext ();
const response = await worker.fetch (request, env, ctx);
await waitOnExecutionContext (ctx);
Prevention : Always wait on execution context in tests.
5. ❌ Error: SELF is not defined Cause : Using old SELF.fetch() pattern instead of direct worker import.
import { SELF } from 'cloudflare:test' ;
await SELF .fetch (request);
import worker from '../src/index' ;
await worker.fetch (request, env, ctx);
Prevention : Use direct worker imports (modern pattern).
6. ❌ Error: KV.get() returned data from previous test Cause : Believing storage is shared (it's not, but may indicate test leak).
Fix : Each test is isolated. If seeing this, check for:
let cache = {};
it ('test 1' , () => { cache.key = 'value' ; });
it ('test 2' , () => { expect (cache.key ).toBeUndefined (); });
it ('test 1' , async () => { await env.CACHE .put ('key' , 'value1' ); });
it ('test 2' , async () => { const val = await env.CACHE .get ('key' ); expect (val).toBeNull (); });
Prevention : Don't use shared variables for test data.
7. ❌ TypeError: env.BUCKET.put is not a function Cause : R2 binding not configured in wrangler.jsonc.
{
"r2_buckets" : [
{ "binding" : "BUCKET" , "bucket_name" : "test-bucket" }
]
}
Prevention : Define all bindings in wrangler config.
8. ❌ Error: Pool 'workers' is not supported Cause : Missing @cloudflare/vitest-pool-workers dependency.
bun add -D @cloudflare/vitest-pool-workers
Prevention : Install pool package for Workers testing.
When to Load References Load reference files for detailed, specialized content:
Load references/vitest-setup.md when:
Setting up Vitest from scratch
Configuring custom pool options
Troubleshooting Miniflare configuration
Migrating from older vitest-pool-workers versions
Load references/binding-mocks.md when:
Testing specific bindings (D1, KV, R2, DO, Queues, AI, Vectorize)
Mocking service bindings (worker-to-worker)
Creating test fixtures for bindings
Understanding isolated storage behavior
Load references/integration-testing.md when:
Writing full request/response tests
Testing multi-step workflows
Simulating production scenarios
Testing WebSocket or streaming responses
Load references/coverage-optimization.md when:
Setting up coverage thresholds
Identifying untested code paths
Optimizing test suite performance
Configuring coverage reporters
Load references/troubleshooting.md when:
Debugging failing tests
Resolving binding errors
Fixing timeout issues
Understanding error messages
Load templates/vitest-config.ts for:
Complete vitest.config.ts example
Advanced configuration options
Multiple wrangler environments
Load templates/basic-test.ts for:
Test file structure template
Common test patterns
beforeEach/afterEach examples
Load templates/binding-mock-test.ts for:
Binding-specific test examples
D1, KV, R2, DO test patterns
Queue and AI testing examples
Load scripts/setup-vitest.sh for:
Automated Vitest installation
Project configuration script
Load scripts/run-tests.sh for:
CI/CD test execution
Coverage reporting automation
Related Cloudflare Plugins For service-specific testing patterns, load:
cloudflare-d1 - D1 database testing, migrations, seeding
cloudflare-kv - KV namespace testing, TTL verification
cloudflare-r2 - R2 bucket testing, file upload/download
cloudflare-durable-objects - DO testing, WebSocket testing
cloudflare-queues - Queue testing, batch processing
cloudflare-workers-ai - AI model testing, inference mocking
This skill focuses on cross-cutting Workers testing patterns applicable to ALL binding types and Workers features.
Questions? Load references/troubleshooting.md or use /workers-debug command for interactive help.