本指南将引导你开始使用 @cloudflare/vitest-pool-workers 包。有关使用 @cloudflare/vitest-pool-workers 进行更复杂测试的示例,请参阅示例。
首先,请确保:
-
你的兼容性日期设置为
2022-10-31或更晚。 -
你的 Worker 使用 ES modules 格式(如果不是,请参阅迁移到 ES modules 格式指南)。
-
Vitest 和
@cloudflare/vitest-pool-workers已作为开发依赖安装在你的项目中npm i -D vitest@^4.1.0 @cloudflare/vitest-pool-workersyarn add -D vitest@^4.1.0 @cloudflare/vitest-pool-workerspnpm add -D vitest@^4.1.0 @cloudflare/vitest-pool-workersbun add -d vitest@^4.1.0 @cloudflare/vitest-pool-workers
在 vitest.config.ts 文件中,使用 cloudflareTest() 插件配置 Workers Vitest 集成。
你可以通过 wrangler.configPath 指定 Wrangler 配置文件中的 Worker 配置。
import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: "./wrangler.jsonc" },
}),
],
});你也可以使用 miniflare 键覆盖或定义额外配置。此配置优先于 Wrangler 配置中设置的值。
例如,此配置会添加一个 KV 命名空间 TEST_NAMESPACE,该命名空间仅在测试中访问和修改。
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: "./wrangler.jsonc" },
miniflare: {
kvNamespaces: ["TEST_NAMESPACE"],
},
}),
],
});有关可用 Miniflare 选项的完整列表,请参阅 Miniflare WorkersOptions API 文档 ↗。
有关可用配置选项的完整列表,请参阅配置。
如果你不使用 TypeScript,可以跳过本节。
首先确保已运行 wrangler types,它会生成 Cloudflare Workers 运行时的类型以及基于 Worker 绑定的 Env 类型。
然后在 tests 文件夹中添加 tsconfig.json,并将 "@cloudflare/vitest-pool-workers" 添加到 types 数组中,以定义 cloudflare:test 的类型。
你还应将 wrangler types 的输出添加到 include 数组中,以便 Cloudflare Workers 运行时的类型可用。
示例 test/tsconfig.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
"types": [
"@cloudflare/vitest-pool-workers/types", // provides `cloudflare:test` and `cloudflare:workers` types
],
},
"include": [
"./**/*.ts",
"../src/worker-configuration.d.ts", // output of `wrangler types`
],
}我们将使用这个简单的 Worker 作为示例。它对 /404 路径返回 404 响应,对所有其他路径返回 "Hello World!"。
export default {
async fetch(request, env, ctx) {
if (pathname === "/404") {
return new Response("Not found", { status: 404 });
}
return new Response("Hello World!");
},
};export default {
async fetch(request, env, ctx): Promise<Response> {
if (pathname === "/404") {
return new Response("Not found", { status: 404 });
}
return new Response("Hello World!");
},
} satisfies ExportedHandler<Env>;通过导入 Worker,我们可以为其 fetch 处理程序编写单元测试。
import { env } from "cloudflare:workers";
import {
createExecutionContext,
waitOnExecutionContext,
} from "cloudflare:test";
import { describe, it, expect } from "vitest";
// Import your worker so you can unit test it
import worker from "../src";
// For now, you'll need to do something like this to get a correctly-typed
// `Request` to pass to `worker.fetch()`.
const IncomingRequest = Request;
describe("Hello World worker", () => {
it("responds with Hello World!", async () => {
const request = new IncomingRequest("http://example.com/404");
// Create an empty context to pass to `worker.fetch()`
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});import { env } from "cloudflare:workers";
import {
createExecutionContext,
waitOnExecutionContext,
} from "cloudflare:test";
import { describe, it, expect } from "vitest";
// Import your worker so you can unit test it
import worker from "../src";
// For now, you'll need to do something like this to get a correctly-typed
// `Request` to pass to `worker.fetch()`.
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>;
describe("Hello World worker", () => {
it("responds with Hello World!", async () => {
const request = new IncomingRequest("http://example.com/404");
// Create an empty context to pass to `worker.fetch()`
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});你可以使用 cloudflare:workers 提供的 exports 对象编写集成测试。exports.default.fetch() 调用主 Worker 中定义的默认导出处理程序。
import { exports } from "cloudflare:workers";
import { describe, it, expect } from "vitest";
describe("Hello World worker", () => {
it("responds with not found and proper status for /404", async () => {
const response = await exports.default.fetch("http://example.com/404");
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});import { exports } from "cloudflare:workers";
import { describe, it, expect } from "vitest";
describe("Hello World worker", () => {
it("responds with not found and proper status for /404", async () => {
const response = await exports.default.fetch("http://example.com/404");
expect(response.status).toBe(404);
expect(await response.text()).toBe("Not found");
});
});使用 exports.default.fetch() 进行集成测试时,你的 Worker 代码在与测试运行器相同的上下文中运行。这意味着你可以使用全局 mock 来控制 Worker,但也意味着 Worker 使用 Vite 提供的略有不同的模块解析行为。
通常这不是问题,但为了在尽可能接近生产环境的新环境中运行 Worker,你可以使用辅助 Worker。请参阅此示例 ↗了解如何使用辅助 Worker 设置集成测试。但是,使用辅助 Worker 存在限制,你需要了解。