跳转到内容
搜索文档

编写测试

最后更新 查看 MarkdownAgent 设置

本指南将介绍如何设置 Miniflare 来测试你的 Workers。Miniflare 是一个底层 API,可让你完全控制 Worker 的运行和测试方式。

要使用 Miniflare,请确保已安装最新版本的 Miniflare v3:

npm i -D miniflare@latest

本指南其余部分使用 node:test 测试框架演示概念,但任何测试框架均可使用。

Miniflare 是一个底层 API,提供大量配置选项来运行 Worker。在大多数情况下,你的测试只需要可用选项的子集,但你可以参考完整 API 参考了解 Miniflare 的全部能力。

在编写测试之前,你需要创建一个 Worker。由于 Miniflare 是模拟 Cloudflare 平台原语的底层 API,你的 Worker 需要用 JavaScript 编写,或者你需要在测试设置中集成自己的构建流水线。以下是一个纯 JavaScript Worker 示例:

src/index.jsjs
export default {
	async fetch(request) {
		return new Response(`Hello World`);
	},
};

接下来,你需要创建初始测试文件:

src/index.test.jsjs
import assert from "node:assert";
import test, { after, before, describe } from "node:test";
import { Miniflare } from "miniflare";

describe("worker", () => {
	/**
	 * @type {Miniflare}
	 */
	let worker;

	before(async () => {
		worker = new Miniflare({
			modules: [
				{
					type: "ESModule",
					path: "src/index.js",
				},
			],
		});
		await worker.ready;
	});

	test("hello world", async () => {
		assert.strictEqual(
			await (await worker.dispatchFetch("http://example.com")).text(),
			"Hello World",
		);
	});

	after(async () => {
		await worker.dispose();
	});
});

你可以通过 node --test 运行上述测试

上面测试文件中高亮的行演示了如何设置 Miniflare 来运行 JavaScript Worker。Miniflare 设置完成后,各个测试可以向运行中的 Worker 发送请求并对响应进行断言。与 Vitest 集成 相比,这是使用 Miniflare 测试 Worker 的主要限制——所有对 Worker 的访问都必须通过 dispatchFetch() Miniflare API,且无法对 Worker 中的单个函数进行单元测试。

测试运行在哪个运行时中?

使用 Vitest 集成 时, 整个测试套件运行在 workerd 中,因此可以对 单个函数进行单元测试。相比之下,使用其他测试框架通过 Miniflare 运行测试时,只有 Worker 本身运行在 workerd 中——你的 测试文件运行在 Node.js 中。这意味着从 Worker 导入函数到测试文件中可能会表现出与运行时不同的行为,如果函数依赖 workerd 特有的行为。

与绑定(binding)交互

Miniflare 的 dispatchFetch() API 允许你向 Worker 发送请求并断言返回正确的响应,但有时你需要在测试中直接与绑定(binding)交互。对于此类用例,Miniflare 提供了 getBindings() API。例如,要在测试中访问环境变量,可按如下方式修改测试文件 src/index.test.js

src/index.test.jsdiff
...
describe("worker", () => {
	...
	before(async () => {
		worker = new Miniflare({
			...
+			bindings: {
+				FOO: "Hello Bindings",
+			},
		});
		...
	});

	test("text binding", async () => {
		const bindings = await worker.getBindings();
		assert.strictEqual(bindings.FOO, "Hello Bindings");
	});
	...
});

你也可以使用与 Worker 中相同的 API 与 KV、R2 等本地资源交互。例如,以下是与 KV 命名空间交互的方式:

src/index.test.jsdiff
...
describe("worker", () => {
	...
	before(async () => {
		worker = new Miniflare({
			...
+			kvNamespaces: ["KV"],
		});
		...
	});

	test("kv binding", async () => {
		const bindings = await worker.getBindings();
		await bindings.KV.put("key", "value");
		assert.strictEqual(await bindings.KV.get("key"), "value");
	});
	...
});

更复杂的 Workers

上面的示例展示了如何测试由单个 JavaScript 文件组成的简单 Worker。然而,大多数实际 Worker 比这更复杂。Miniflare 支持通过 API 直接提供 Worker 的所有组成文件:

new Miniflare({
	modules: [
		{
			type: "ESModule",
			path: "src/index.js",
		},
		{
			type: "ESModule",
			path: "src/imported.js",
		},
	],
});

随着 Worker 规模增长,这可能会有些繁琐。为此,Miniflare 还可以遍历模块图以自动确定要包含哪些模块:

new Miniflare({
	scriptPath: "src/index-with-imports.js",
	modules: true,
	modulesRules: [{ type: "ESModule", include: ["**/*.js"] }],
});

自定义构建

在许多实际场景中,Worker 并非用纯 JavaScript 编写,而是由多个 TypeScript 文件组成,这些文件从 npm 包和其他依赖项导入,然后由构建工具打包。通过 Miniflare 直接测试 Worker 时,你需要在测试之前运行此构建工具。具体如何运行取决于你使用的测试框架,但对于 node:test,很可能在 setup() 钩子中运行。例如,如果你使用 Wrangler 构建和部署 Worker,可以像这样生成 wrangler build 命令:

before(() => {
	spawnSync("npx wrangler build -c wrangler-build.json", {
		shell: true,
		stdio: "pipe",
	});
});

这篇文档对您有帮助吗?