cloudflare-workers-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Workers Testing with Vitest

使用Vitest测试Cloudflare Workers

Status: ✅ Production Ready | Last Verified: 2025-01-27 Vitest: 2.1.8 | @cloudflare/vitest-pool-workers: 0.7.2 | Miniflare: Latest
状态: ✅ 可用于生产环境 | 最后验证: 2025-01-27 Vitest: 2.1.8 | @cloudflare/vitest-pool-workers: 0.7.2 | Miniflare: 最新版本

Table of Contents

目录

What Is Workers Testing?

什么是Workers测试?

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.

使用Vitest和**@cloudflare/vitest-pool-workers**测试Cloudflare Workers,能够编写在真实Workers环境中运行的单元测试和集成测试,全面支持各类绑定(D1、KV、R2、Durable Objects、Queues、AI)。测试在Miniflare中执行以用于本地开发,也可在CI/CD中运行,匹配Workers实际运行时行为。
核心能力: 绑定模拟、执行上下文测试、边缘运行时模拟、覆盖率追踪、快速测试执行。

New in 2025

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
undefined
@cloudflare/vitest-pool-workers 0.7.2(2025年1月):
  • 重大变更: Miniflare v3 → 要求Node.js 18+
  • 新增:
    cloudflare:test
    模块,用于访问环境/上下文
  • 优化: 绑定的隔离存储速度提升
  • 修复: Worker间服务绑定功能现在可正常工作
  • 新增: 支持Vectorize和Workers AI绑定
从旧版本迁移:
bash
undefined

Update dependencies

更新依赖

bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2
bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2

Update vitest.config.ts (new pool configuration format)

更新vitest.config.ts(新的池配置格式)

export default defineWorkersConfig({ test: { poolOptions: { workers: { wrangler: { configPath: './wrangler.jsonc' }, miniflare: { compatibilityDate: '2025-01-27' } } } } });

---
export default defineWorkersConfig({ test: { poolOptions: { workers: { wrangler: { configPath: './wrangler.jsonc' }, miniflare: { compatibilityDate: '2025-01-27' } } } } });

---

Quick Start (5 Minutes)

快速入门(5分钟)

1. Install Dependencies

1. 安装依赖

bash
bun add -D vitest @cloudflare/vitest-pool-workers
bash
bun add -D vitest @cloudflare/vitest-pool-workers

2. Create
vitest.config.ts

2. 创建
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']
        }
      }
    }
  }
});
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

3. 编写第一个测试用例

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);
  });
});
typescript
import { describe, it, expect } from 'vitest';
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import worker from '../src/index';

describe('Worker', () => {
  it('返回状态码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

4. 运行测试

bash
bun test
bash
bun test

or

bunx vitest

---
bunx vitest

---

Critical Rules

关键规则

1. Always Use
cloudflare:test
for Env Access

1. 始终使用
cloudflare:test
获取环境变量

✅ 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.
✅ 正确做法:
typescript
import { env } from 'cloudflare:test';

it('查询D1数据库', async () => {
  const result = await env.DB.prepare('SELECT * FROM users').all();
  expect(result.results).toHaveLength(0); // 每个测试使用全新隔离的数据库
});
❌ 错误做法:
typescript
// 不要手动创建env对象
const env = { DB: mockDB }; // ❌ 不会使用真实的D1绑定
原因:
cloudflare:test
提供从
wrangler.jsonc
配置的真实绑定,且每个测试使用隔离存储。

2. Always Wait on Execution Context

2. 始终等待执行上下文完成

✅ 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.
✅ 正确做法:
typescript
it('处理异步操作', async () => {
  const ctx = createExecutionContext();
  const response = await worker.fetch(request, env, ctx);
  await waitOnExecutionContext(ctx); // ✅ 确保ctx.waitUntil任务完成

  expect(response.status).toBe(200);
});
❌ 错误做法:
typescript
it('缺少等待步骤', async () => {
  const ctx = createExecutionContext();
  const response = await worker.fetch(request, env, ctx);
  // ❌ 缺少waitOnExecutionContext - ctx.waitUntil任务可能未完成
  expect(response.status).toBe(200);
});
原因: Workers使用
ctx.waitUntil()
处理后台任务(如日志、分析)。如果不等待,这些任务在测试中可能无法完成。

3. Each Test Gets Isolated Storage

3. 每个测试使用隔离存储

✅ 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).
✅ 正确做法:
typescript
describe('KV操作', () => {
  it('测试1:写入KV', async () => {
    await env.CACHE.put('key', 'value1');
    const val = await env.CACHE.get('key');
    expect(val).toBe('value1'); // ✅ 隔离存储
  });

  it('测试2:初始干净状态', async () => {
    const val = await env.CACHE.get('key');
    expect(val).toBeNull(); // ✅ 测试1的数据不会泄露到这里
  });
});
原因: 每个测试运行时都会使用全新的绑定存储(自动隔离)。

4. Use Wrangler Config for Bindings

4. 使用Wrangler配置绑定

✅ 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.
✅ 正确做法:
typescript
// vitest.config.ts
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' } // ✅ 从wrangler读取绑定配置
      }
    }
  }
});
❌ 错误做法:
typescript
// vitest.config.ts
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        // ❌ 未配置wrangler - 绑定将不可用
        miniflare: { compatibilityDate: '2025-01-27' }
      }
    }
  }
});
原因: Wrangler配置定义了所有绑定(D1、KV、R2等)。没有它,
env
将为空。

5. Match Compatibility Date

5. 匹配兼容性日期

✅ 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.

✅ 正确做法:
typescript
// vitest.config.ts
miniflare: {
  compatibilityDate: '2025-01-27' // ✅ 与wrangler.jsonc保持一致
}

// wrangler.jsonc
{
  "compatibility_date": "2025-01-27"
}
原因: 确保测试环境与生产运行时行为一致。

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);
});
D1数据库:
typescript
import { env } from 'cloudflare:test';

it('查询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');
});
KV命名空间:
typescript
it('从KV读取数据', async () => {
  await env.CACHE.put('test-key', 'test-value');
  const value = await env.CACHE.get('test-key');
  expect(value).toBe('test-value');
});
R2存储桶:
typescript
it('上传文件到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('与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

单元测试 vs 集成测试

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');
});
单元测试(单个函数):
typescript
import { validateInput } from '../src/utils/validator';

it('验证输入合法性', () => {
  const result = validateInput({ name: 'Alice', age: 30 });
  expect(result.valid).toBe(true);
});
集成测试(完整fetch处理器):
typescript
import worker from '../src/index';
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';

it('处理完整请求流程', 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

添加到
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
      }
    }
  }
});
运行覆盖率测试:
bash
bunx vitest run --coverage

Top 5 Use Cases

五大核心使用场景

1. Testing API Endpoints with D1

1. 测试带D1的API端点

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');
});
typescript
it('通过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

2. 测试基于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');
});
typescript
it('缓存API响应', 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

3. 测试上传文件到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');
});
typescript
it('处理文件上传', 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);

  // 验证R2上传结果
  const object = await env.BUCKET.get('test.txt');
  expect(await object?.text()).toBe('test content');
});

4. Testing Durable Objects State

4. 测试Durable Objects状态

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);
});
typescript
it('维护计数器状态', async () => {
  const id = env.COUNTER.idFromName('my-counter');
  const stub = env.COUNTER.get(id);

  // 递增3次
  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

5. 测试队列消费者

typescript
it('processes queue messages', async () => {
  const messages = [
    { id: '1', body: { action: 'email', to: 'user@example.com' }, timestamp: new Date() }
  ];

  // Simulate queue batch
  await worker.queue(
    {
      queue: 'my-queue',
      messages,
      retryAll: () => {},
      ackAll: () => {}
    },
    env
  );

  // Verify processing (check DB, logs, etc.)
  const log = await env.DB.prepare('SELECT * FROM email_log WHERE id = ?').bind('1').first();
  expect(log?.status).toBe('sent');
});

typescript
it('处理队列消息', 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

✅ 推荐做法

  1. Use descriptive test names:
    typescript
    it('returns 404 when user not found', async () => {});
    it('validates email format before saving', async () => {});
  2. Test error cases:
    typescript
    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);
    });
  3. Group related tests:
    typescript
    describe('User API', () => {
      describe('POST /users', () => {
        it('creates user with valid data', async () => {});
        it('rejects duplicate email', async () => {});
        it('validates required fields', async () => {});
      });
    });
  4. Use beforeEach for setup:
    typescript
    describe('Database tests', () => {
      beforeEach(async () => {
        // Seed test data
        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);
      });
    });
  5. Test realistic scenarios:
    typescript
    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);
    });
  1. 使用描述性的测试名称:
    typescript
    it('用户不存在时返回404', async () => {});
    it('保存前验证邮箱格式', async () => {});
  2. 测试错误场景:
    typescript
    it('无效JSON时返回400', 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);
    });
  3. 分组相关测试:
    typescript
    describe('用户API', () => {
      describe('POST /users', () => {
        it('有效数据创建用户', async () => {});
        it('拒绝重复邮箱', async () => {});
        it('验证必填字段', async () => {});
      });
    });
  4. 使用beforeEach进行前置设置:
    typescript
    describe('数据库测试', () => {
      beforeEach(async () => {
        // 初始化测试数据
        await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Test User').run();
      });
    
      it('查询用户列表', async () => {
        const result = await env.DB.prepare('SELECT * FROM users').all();
        expect(result.results).toHaveLength(1);
      });
    });
  5. 测试真实场景:
    typescript
    it('处理并发请求', 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

❌ 不推荐做法

  1. Don't share state between tests:
    typescript
    // ❌ BAD: Leaky state
    let counter = 0;
    it('test 1', () => { counter++; });
    it('test 2', () => { expect(counter).toBe(1); }); // Fragile!
    
    // ✅ GOOD: Isolated
    it('test 1', () => { const counter = 0; counter++; });
    it('test 2', () => { const counter = 0; /* fresh state */ });
  2. Don't forget to wait:
    typescript
    // ❌ BAD
    const response = await worker.fetch(request, env, ctx);
    expect(response.status).toBe(200); // ctx.waitUntil not finished
    
    // ✅ GOOD
    const response = await worker.fetch(request, env, ctx);
    await waitOnExecutionContext(ctx);
    expect(response.status).toBe(200);
  3. Don't hardcode URLs:
    typescript
    // ❌ BAD
    const request = new Request('http://example.com/test');
    
    // ✅ GOOD
    const request = new Request('http://fake-host/test'); // Host doesn't matter in tests
  4. Don't test implementation details:
    typescript
    // ❌ BAD: Testing internals
    expect(worker.privateHelperFunction).toBeDefined();
    
    // ✅ GOOD: Testing behavior
    const response = await worker.fetch(request, env, ctx);
    expect(response.status).toBe(200);

  1. 不要在测试间共享状态:
    typescript
    // ❌ 错误:状态泄露
    let counter = 0;
    it('测试1', () => { counter++; });
    it('测试2', () => { expect(counter).toBe(1); }); // 不稳定!
    
    // ✅ 正确:隔离状态
    it('测试1', () => { const counter = 0; counter++; });
    it('测试2', () => { const counter = 0; /* 全新状态 */ });
  2. 不要忘记等待执行上下文:
    typescript
    // ❌ 错误
    const response = await worker.fetch(request, env, ctx);
    expect(response.status).toBe(200); // ctx.waitUntil任务未完成
    
    // ✅ 正确
    const response = await worker.fetch(request, env, ctx);
    await waitOnExecutionContext(ctx);
    expect(response.status).toBe(200);
  3. 不要硬编码URL:
    typescript
    // ❌ 错误
    const request = new Request('http://example.com/test');
    
    // ✅ 正确
    const request = new Request('http://fake-host/test'); // 测试中主机无关紧要
  4. 不要测试实现细节:
    typescript
    // ❌ 错误:测试内部实现
    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

1. ❌
ReferenceError: env is not defined

Cause: Not importing
env
from
cloudflare:test
.
Fix:
typescript
import { env } from 'cloudflare:test'; // ✅ Add this import
Prevention: Always use
cloudflare:test
module for env access.

原因: 未从
cloudflare:test
导入
env
修复:
typescript
import { env } from 'cloudflare:test'; // ✅ 添加此导入
预防: 始终使用
cloudflare:test
模块获取环境变量。

2. ❌
TypeError: Cannot read property 'DB' of undefined

2. ❌
TypeError: Cannot read property 'DB' of undefined

Cause:
wrangler.jsonc
not loaded in
vitest.config.ts
.
Fix:
typescript
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' } // ✅ Add this
      }
    }
  }
});
Prevention: Always configure wrangler path in vitest config.

原因:
vitest.config.ts
中未加载
wrangler.jsonc
修复:
typescript
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' } // ✅ 添加此配置
      }
    }
  }
});
预防: 始终在vitest配置中指定wrangler路径。

3. ❌
Error: D1_ERROR: no such table: users

3. ❌
Error: D1_ERROR: no such table: users

Cause: D1 database schema not applied in tests.
Fix:
typescript
// Option 1: Seed in beforeEach
beforeEach(async () => {
  await env.DB.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL
    )
  `);
});

// Option 2: Use migrations (load from file)
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.

原因: 测试中未应用D1数据库 schema。
修复:
typescript
// 选项1:在beforeEach中初始化
beforeEach(async () => {
  await env.DB.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL
    )
  `);
});

// 选项2:使用迁移(从文件加载)
beforeEach(async () => {
  const schema = await fs.readFile('./migrations/schema.sql', 'utf-8');
  await env.DB.exec(schema);
});
预防: 每个测试前创建schema,或使用共享前置设置。

4. ❌
Error: ctx.waitUntil tasks did not complete

4. ❌
Error: ctx.waitUntil tasks did not complete

Cause: Missing
await waitOnExecutionContext(ctx)
.
Fix:
typescript
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ Add this
Prevention: Always wait on execution context in tests.

原因: 缺少
await waitOnExecutionContext(ctx)
修复:
typescript
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ 添加此步骤
预防: 测试中始终等待执行上下文完成。

5. ❌
Error: SELF is not defined

5. ❌
Error: SELF is not defined

Cause: Using old
SELF.fetch()
pattern instead of direct worker import.
Fix:
typescript
// ❌ OLD (vitest-pool-workers <0.5)
import { SELF } from 'cloudflare:test';
await SELF.fetch(request);

// ✅ NEW (vitest-pool-workers ≥0.7)
import worker from '../src/index';
await worker.fetch(request, env, ctx);
Prevention: Use direct worker imports (modern pattern).

原因: 使用旧的
SELF.fetch()
模式,而非直接导入worker。
修复:
typescript
// ❌ 旧模式(vitest-pool-workers <0.5)
import { SELF } from 'cloudflare:test';
await SELF.fetch(request);

// ✅ 新模式(vitest-pool-workers ≥0.7)
import worker from '../src/index';
await worker.fetch(request, env, ctx);
预防: 使用直接导入worker的现代模式。

6. ❌
Error: KV.get() returned data from previous test

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:
typescript
// ❌ Test pollution (shared variable)
let cache = {};
it('test 1', () => { cache.key = 'value'; });
it('test 2', () => { expect(cache.key).toBeUndefined(); }); // Fails!

// ✅ Proper isolation
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.

原因: 误以为存储是共享的(实际是隔离的,此错误可能表示测试状态泄露)。
修复: 每个测试都是隔离的。如果遇到此问题,检查是否存在:
typescript
// ❌ 测试污染(共享变量)
let cache = {};
it('测试1', () => { cache.key = 'value'; });
it('测试2', () => { expect(cache.key).toBeUndefined(); }); // 失败!

// ✅ 正确隔离
it('测试1', async () => { await env.CACHE.put('key', 'value1'); });
it('测试2', async () => { const val = await env.CACHE.get('key'); expect(val).toBeNull(); });
预防: 不要使用共享变量存储测试数据。

7. ❌
TypeError: env.BUCKET.put is not a function

7. ❌
TypeError: env.BUCKET.put is not a function

Cause: R2 binding not configured in
wrangler.jsonc
.
Fix:
jsonc
// wrangler.jsonc
{
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "test-bucket" }
  ]
}
Prevention: Define all bindings in wrangler config.

原因:
wrangler.jsonc
中未配置R2绑定。
修复:
jsonc
// wrangler.jsonc
{
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "test-bucket" }
  ]
}
预防: 在wrangler配置中定义所有绑定。

8. ❌
Error: Pool 'workers' is not supported

8. ❌
Error: Pool 'workers' is not supported

Cause: Missing
@cloudflare/vitest-pool-workers
dependency.
Fix:
bash
bun add -D @cloudflare/vitest-pool-workers
Prevention: Install pool package for Workers testing.

原因: 缺少
@cloudflare/vitest-pool-workers
依赖。
修复:
bash
bun add -D @cloudflare/vitest-pool-workers
预防: 安装Workers测试专用的池包。

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

加载参考文档以获取详细的专业内容:
当以下情况时,加载
references/vitest-setup.md
:
  • 从零开始设置Vitest
  • 配置自定义池选项
  • 排查Miniflare配置问题
  • 从旧版本vitest-pool-workers迁移
当以下情况时,加载
references/binding-mocks.md
:
  • 测试特定绑定(D1、KV、R2、DO、Queues、AI、Vectorize)
  • 模拟服务绑定(Worker间调用)
  • 为绑定创建测试固定数据
  • 理解隔离存储行为
当以下情况时,加载
references/integration-testing.md
:
  • 编写完整的请求/响应测试
  • 测试多步骤工作流
  • 模拟生产场景
  • 测试WebSocket或流式响应
当以下情况时,加载
references/coverage-optimization.md
:
  • 设置覆盖率阈值
  • 识别未测试的代码路径
  • 优化测试套件性能
  • 配置覆盖率报告器
当以下情况时,加载
references/troubleshooting.md
:
  • 调试失败的测试
  • 解决绑定错误
  • 修复超时问题
  • 理解错误信息
加载
templates/vitest-config.ts
用于
:
  • 完整的vitest.config.ts示例
  • 高级配置选项
  • 多wrangler环境配置
加载
templates/basic-test.ts
用于
:
  • 测试文件结构模板
  • 常见测试模式
  • beforeEach/afterEach示例
加载
templates/binding-mock-test.ts
用于
:
  • 绑定专用测试示例
  • D1、KV、R2、DO测试模式
  • Queue和AI测试示例
加载
scripts/setup-vitest.sh
用于
:
  • 自动化Vitest安装
  • 项目配置脚本
加载
scripts/run-tests.sh
用于
:
  • CI/CD测试执行
  • 覆盖率报告自动化

Related Cloudflare Plugins

相关Cloudflare插件

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.
如需特定服务的测试模式,加载:
  • cloudflare-d1 - D1数据库测试、迁移、数据初始化
  • cloudflare-kv - KV命名空间测试、TTL验证
  • cloudflare-r2 - R2存储桶测试、文件上传/下载
  • cloudflare-durable-objects - DO测试、WebSocket测试
  • cloudflare-queues - Queue测试、批量处理
  • cloudflare-workers-ai - AI模型测试、推理模拟
本指南聚焦于跨场景的Workers测试模式,适用于所有绑定类型和Workers功能。

有疑问? 加载
references/troubleshooting.md
或使用
/workers-debug
命令获取交互式帮助。