cloudflare-workers-testing — quality + safety report
In the Skillier index (secondsky__cloudflare-workers-testing) · scanned 2026-06-03 · engine: builtin+triage
1 heuristic flag to review
Heuristic flags from the builtin scanner, which is known to over-flag (it trips on legitimate env-reading integrations, security skills, and library .eval calls). This is NOT an authoritative malicious verdict — re-scan with SkillSpector for the authoritative result. Run the authoritative scan →
📇 This skill is in the Skillier index (curated · deduped · quality-filtered). Install Skillier to route & load it into your AI client.
Quality notes
About this skill
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.
📄 Read the SKILL.md
---
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?](#what-is-workers-testing)
- [New in 2025](#new-in-2025)
- [Quick Start (5 Minutes)](#quick-start-5-minutes)
- [Critical Rules](#critical-rules)
- [Core Concepts](#core-concepts)
- [Top 5 Use Cases](#top-5-use-cases)
- [Best Practices](#best-practices)
- [Top 8 Errors Prevented](#top-8-errors-prevented)
- [When to Load References](#when-to-load-references)
---
## 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**:
```bash
# Update dependencies
bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2
# Update vitest.config.ts (new pool configuration format)
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
miniflare: { compatibilityDate: '2025-01-27' }
}
}
}
});
```
---
## Quick Start (5 Minutes)
### 1. Install Dependencies
```bash
bun add -D vitest @cloudflare/vitest-pool-workers
```
### 2. Create `vitest.config.ts`
```typescript
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
```typescript
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
```bash
bun test
# or
bunx vitest
```
---
## Critical Rules
### 1. Always Use `cloudflare:test` for Env Access
**✅ CORRECT**:
```typescript
import { env } from 'cloudflare:test';
it('queries D1', async () => {
const result = await env.DB.prepare('SELECT * FROM users').all();
expect(result.results).toHaveLength(0); // Fresh isolated DB per test
});
```
**❌ WRONG**:
```typescript
// Don't manually create env object
const env = { DB: mockDB }; // ❌ Won't use real D1 binding
```
**Why**: `cloudflare:test` provides real bindings configured from `wrangler.jsonc` with isolated storage per test.
### 2. Always Wait on Execution Context
**✅ CORRECT**:
```typescript
it('handles async operations', async () => {
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ Ensures ctx.waitUntil completes
expect(response.status).toBe(200);
});
```
**❌ WRONG**:
```typescript
it('missing wait', async () => {
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// ❌ Missing waitOnExecutionContext - ctx.waitUntil tasks may not complete
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
**✅ CORRECT**:
```typescript
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'); // ✅ Isolated
});
it('test 2: clean state', async () => {
const val = await env.CACHE.get('key');
expect(val).toBeNull(); // ✅ Test 1's data doesn't leak here
});
});
```
**Why**: Each test runs with fresh binding storage (automatic isolation).
### 4. Use Wrangler Config for Bindings
**✅ CORRECT**:
```typescript
// vitest.config.ts
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' } // ✅ Reads bindings from wrangler
}
}
}
});
```
**❌ WRONG**:
```typescript
// vitest.config.ts
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
// ❌ No wrangler config - bindings won't be available
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
**✅ CORRECT**:
```typescript
// vitest.config.ts
miniflare: {
compatibilityDate: '2025-01-27' // ✅ Matches wrangler.jsonc
}
// wrangler.jsonc
{
"compatibility_date": "2025-01-27"
}
```
**Why**: Ensures test environment matches production runtime behavior.
---
## Core Concepts
### Binding Testing Patterns
**D1 Database**:
```typescript
import { env } from 'cloudflare:test';
it('queries D1', async () => {
// Insert test data
await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Alice').run();
// Query
const result = await env.DB.prepare('SELECT * FROM users WHERE name = ?').bind('Alice').first();
expect(result?.name).toBe('Alice');
});
```
**KV Namespace**:
```typescript
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');
});
```
**R2 Bucket**:
```typescript
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');
});
```
**Durable Objects**:
```typescript
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):
```typescript
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):
```typescript
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
Add to `vitest.config.ts`:
```typescript
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
}
}
}
});
```
Run with coverage:
```bash
bunx vitest run --coverage
```
---
## Top 5 Use Cases
### 1. Testing API Endpoints with D1
```typescript
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);
// Verify DB insert
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
```typescript
it('caches API responses', async () => {
// First request (cache miss)
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');
// Second request (cache hit)
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
```typescript
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);
// Verify R2 upload
const object = await env.BUCKET.get('test.txt');
expect(await object?.text()).toBe('test content');
});
```
### 4. Testing Durable Objects State
```typescript
it('maintains counter state', async () => {
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);
// Increment 3 times
for (let i = 0; i < 3; i++) {
await stub.fetch('http://fake/increment');
}
// Verify state
const response = await stub.fetch('http://fake/value');
const data = await response.json();
expect(data.count).toBe(3);
});
```
### 5. Testing Queue Consumers
```typescript
it('processes queue messages', async () => {
const messages = [
{ id: '1', body: { action: 'em
… (truncated)Want a live grade + an embeddable README badge? Run your skill through the free scanner.
Graded independently by Skillproof — nothing to sell the author. Quality is mechanical + corpus-grounded; safety flags are heuristic (builtin+triage), not a malicious verdict.