跳转到内容
搜索文档

编写你的第一个测试

最后更新 查看 MarkdownAgent 设置

本指南将引导你开始使用 @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-workers

定义 Vitest 配置

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

test/tsconfig.jsonjsonc
{
	"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!"

src/index.jsjs
export default {
	async fetch(request, env, ctx) {
		if (pathname === "/404") {
			return new Response("Not found", { status: 404 });
		}
		return new Response("Hello World!");
	},
};
src/index.tsts
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 处理程序编写单元测试。

test/unit.spec.jsjs
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");
	});
});
test/unit.spec.tsts
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 中定义的默认导出处理程序。

test/integration.spec.jsjs
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");
	});
});
test/integration.spec.tsts
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 存在限制,你需要了解。

相关资源

这篇文档对您有帮助吗?