CloudflareWorkers测试指南 workers-testing

这个技能提供了使用 Vitest 和 @cloudflare/vitest-pool-workers 进行 Cloudflare Workers 全面测试的指南。它覆盖了测试设置、绑定模拟(如 D1 数据库、KV 命名空间、R2 存储、Durable Objects 等)、集成测试、覆盖率跟踪和错误预防。适用于无服务器函数测试,帮助开发者确保代码质量。关键词:Cloudflare Workers、测试、Vitest、无服务器、绑定模拟、单元测试、集成测试。

测试 0 次安装 0 次浏览 更新于 3/7/2026

名称:workers-testing 描述:使用 Vitest 和 @cloudflare/vitest-pool-workers 的 Cloudflare Workers 全面测试指南。用于测试设置、绑定模拟(D1/KV/R2/DO)、集成测试,或遇到测试失败、模拟错误、覆盖率问题。 关键词:

  • 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 许可证:MIT 元数据: 版本:“1.0.0” 最后验证:“2025-01-27” 生产测试:true 令牌节省:“~70%” 错误预防:8 包含模板:3 包含参考:5 包含脚本:2 vitest版本:“2.1.8” workers_types版本:“4.20251125.0” vitest_pool_workers版本:“0.7.2”

Cloudflare Workers 使用 Vitest 进行测试

状态:✅ 生产就绪 | 最后验证:2025-01-27 Vitest:2.1.8 | @cloudflare/vitest-pool-workers:0.7.2 | Miniflare:最新

目录


什么是 Workers 测试?

使用 Vitest@cloudflare/vitest-pool-workers 测试 Cloudflare Workers,可以编写在真实 Workers 环境中运行的单元和集成测试,支持所有绑定(D1、KV、R2、Durable Objects、Queues、AI)。测试在 Miniflare 中执行以进行本地开发,并可以在 CI/CD 中运行,模拟实际 Workers 运行时行为。

关键能力:绑定模拟、执行上下文测试、边缘运行时模拟、覆盖率跟踪、快速测试执行。


2025年新内容

@cloudflare/vitest-pool-workers 0.7.2(2025年1月):

  • 重大变更:Miniflare v3 → 需要 Node.js 18+
  • 新增cloudflare:test 模块用于环境/上下文访问
  • 改进:绑定隔离存储更快
  • 修复:Worker 到 Worker 服务绑定现在正确工作
  • 新增:支持 Vectorize 和 Workers AI 绑定

从旧版本迁移

# 更新依赖
bun add -D vitest@^2.1.8 @cloudflare/vitest-pool-workers@^0.7.2

# 更新 vitest.config.ts(新池配置格式)
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' },
        miniflare: { compatibilityDate: '2025-01-27' }
      }
    }
  }
});

快速开始(5分钟)

1. 安装依赖

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

2. 创建 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. 编写你的第一个测试

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. 运行测试

bun test
# 或
bunx vitest

关键规则

1. 始终使用 cloudflare:test 访问环境

✅ 正确

import { env } from 'cloudflare:test';

it('查询 D1', async () => {
  const result = await env.DB.prepare('SELECT * FROM users').all();
  expect(result.results).toHaveLength(0); // 每个测试有新的隔离数据库
});

❌ 错误

// 不要手动创建环境对象
const env = { DB: mockDB }; // ❌ 不会使用真实的 D1 绑定

原因cloudflare:test 提供从 wrangler.jsonc 配置的真实绑定,每个测试有隔离存储。

2. 始终等待执行上下文

✅ 正确

it('处理异步操作', async () => {
  const ctx = createExecutionContext();
  const response = await worker.fetch(request, env, ctx);
  await waitOnExecutionContext(ctx); // ✅ 确保 ctx.waitUntil 完成

  expect(response.status).toBe(200);
});

❌ 错误

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. 每个测试都有隔离存储

✅ 正确

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. 使用 Wrangler 配置绑定

✅ 正确

// vitest.config.ts
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' } // ✅ 从 wrangler 读取绑定
      }
    }
  }
});

❌ 错误

// vitest.config.ts
export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        // ❌ 没有 wrangler 配置 - 绑定不可用
        miniflare: { compatibilityDate: '2025-01-27' }
      }
    }
  }
});

原因:Wrangler 配置定义了所有绑定(D1、KV、R2 等)。没有它,env 将为空。

5. 匹配兼容性日期

✅ 正确

// vitest.config.ts
miniflare: {
  compatibilityDate: '2025-01-27' // ✅ 匹配 wrangler.jsonc
}

// wrangler.jsonc
{
  "compatibility_date": "2025-01-27"
}

原因:确保测试环境匹配生产运行时行为。


核心概念

绑定测试模式

D1 数据库

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 命名空间

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 存储桶

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

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);
});

单元测试与集成测试

单元测试(单一函数):

import { validateInput } from '../src/utils/validator';

it('验证输入', () => {
  const result = validateInput({ name: 'Alice', age: 30 });
  expect(result.valid).toBe(true);
});

集成测试(完整 fetch 处理程序):

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');
});

覆盖率配置

添加到 vitest.config.ts

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

前5大用例

1. 使用 D1 测试 API 端点

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. 使用 KV 测试缓存

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. 测试文件上传到 R2

it('处理文件上传', async () => {
  const formData = new FormData();
  formData.append('file', new Blob(['测试内容'], { 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('测试内容');
});

4. 测试 Durable Objects 状态

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. 测试队列消费者

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');
});

最佳实践

✅ 建议

  1. 使用描述性测试名称

    it('用户不存在时返回 404', async () => {});
    it('保存前验证电子邮件格式', async () => {});
    
  2. 测试错误情况

    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. 分组相关测试

    describe('用户 API', () => {
      describe('POST /users', () => {
        it('使用有效数据创建用户', async () => {});
        it('拒绝重复电子邮件', async () => {});
        it('验证必填字段', async () => {});
      });
    });
    
  4. 使用 beforeEach 进行设置

    describe('数据库测试', () => {
      beforeEach(async () => {
        // 播种测试数据
        await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('测试用户').run();
      });
    
      it('查询用户', async () => {
        const result = await env.DB.prepare('SELECT * FROM users').all();
        expect(result.results).toHaveLength(1);
      });
    });
    
  5. 测试实际场景

    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);
    });
    

❌ 不建议

  1. 不要在测试之间共享状态

    // ❌ 错误:泄露状态
    let counter = 0;
    it('测试 1', () => { counter++; });
    it('测试 2', () => { expect(counter).toBe(1); }); // 脆弱!
    
    // ✅ 正确:隔离
    it('测试 1', () => { const counter = 0; counter++; });
    it('测试 2', () => { const counter = 0; /* 新状态 */ });
    
  2. 不要忘记等待

    // ❌ 错误
    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

    // ❌ 错误
    const request = new Request('http://example.com/test');
    
    // ✅ 正确
    const request = new Request('http://fake-host/test'); // 主机在测试中不重要
    
  4. 不要测试实现细节

    // ❌ 错误:测试内部
    expect(worker.privateHelperFunction).toBeDefined();
    
    // ✅ 正确:测试行为
    const response = await worker.fetch(request, env, ctx);
    expect(response.status).toBe(200);
    

前8大预防错误

1. ❌ ReferenceError: env 未定义

原因:未从 cloudflare:test 导入 env

修复

import { env } from 'cloudflare:test'; // ✅ 添加此导入

预防:始终使用 cloudflare:test 模块访问环境。

2. ❌ TypeError: 无法读取未定义属性的 'DB'

原因vitest.config.ts 中未加载 wrangler.jsonc

修复

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.jsonc' } // ✅ 添加此配置
      }
    }
  }
});

预防:始终在 vitest 配置中配置 wrangler 路径。

3. ❌ Error: D1_ERROR: 表不存在: users

原因:测试中未应用 D1 数据库架构。

修复

// 选项 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);
});

预防:在每个测试前创建架构或使用共享设置。

4. ❌ Error: ctx.waitUntil 任务未完成

原因:缺少 await waitOnExecutionContext(ctx)

修复

const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx); // ✅ 添加此语句

预防:始终在测试中等待执行上下文。

5. ❌ Error: SELF 未定义

原因:使用旧的 SELF.fetch() 模式而不是直接导入 worker。

修复

// ❌ 旧(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() 返回了前一个测试的数据

原因:认为存储是共享的(不是,但可能表示测试泄漏)。

修复:每个测试都是隔离的。如果看到此错误,检查:

// ❌ 测试污染(共享变量)
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 不是函数

原因:R2 绑定未在 wrangler.jsonc 中配置。

修复

// wrangler.jsonc
{
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "test-bucket" }
  ]
}

预防:在 wrangler 配置中定义所有绑定。

8. ❌ Error: 池 'workers' 不支持

原因:缺少 @cloudflare/vitest-pool-workers 依赖。

修复

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

预防:安装池包用于 Workers 测试。


何时加载参考

为详细、专业内容加载参考文件:

加载 references/vitest-setup.md 当:

  • 从头设置 Vitest
  • 配置自定义池选项
  • 排除 Miniflare 配置故障
  • 从旧版 vitest-pool-workers 迁移

加载 references/binding-mocks.md 当:

  • 测试特定绑定(D1、KV、R2、DO、Queues、AI、Vectorize)
  • 模拟服务绑定(worker-to-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 测试模式
  • 队列和 AI 测试示例

加载 scripts/setup-vitest.sh 用于:

  • 自动化 Vitest 安装
  • 项目配置脚本

加载 scripts/run-tests.sh 用于:

  • CI/CD 测试执行
  • 覆盖率报告自动化

相关 Cloudflare 插件

用于服务特定测试模式,加载:

  • cloudflare-d1 - D1 数据库测试、迁移、播种
  • cloudflare-kv - KV 命名空间测试、TTL 验证
  • cloudflare-r2 - R2 存储桶测试、文件上传/下载
  • cloudflare-durable-objects - DO 测试、WebSocket 测试
  • cloudflare-queues - 队列测试、批处理
  • cloudflare-workers-ai - AI 模型测试、推理模拟

此技能专注于适用于所有绑定类型和 Workers 功能的交叉测试模式


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