用途
meteor-testing 用于在 Meteor 3 应用中搭建、设计、编写或修复测试与测试框架。当出现 meteortesting:mocha、--driver-package 等触发条件时使用。
meteortesting:mocha 是 Meteor 的标准测试驱动。它以测试模式启动应用(除测试文件外不运行应用代码),并在服务器控制台报告结果。
如果现有测试失败、挂起或出现不稳定,且不清楚失败层级,应先用 meteor-debugging 隔离原因;当证据指向测试搭建、设计、驱动行为、fixtures 或回归覆盖时,再回到本技能。
决策流程
- 现有测试出现未知失败?使用 meteor-debugging 归类应用、数据、测试框架、浏览器、环境或共享状态等原因。
- 纯逻辑、不涉及 Meteor API?使用普通 Mocha 或任意测试运行器,无需 Meteor 特定内容。
- 方法或发布?用 meteortesting:mocha 做集成测试,使用 Meteor.server.method_handlers[name].apply(ctx, args) 或 publish_handlers。
- 需要驱动真实 DDP 客户端(跨进程或端到端 DDP 语义)?使用 --full-app 模式和 DDP.connect。
- UI 流程 / 浏览器交互?以确定性测试设置启动普通应用,对其运行 Playwright 或 Cypress。仅当浏览器流程必须加载 app-test 模块时,才使用 meteor test --full-app。
安装与脚本
meteor add meteortesting:mocha
meteor npm install --save-dev @types/mocha
package.json:
{
"scripts": {
"test": "TEST_WATCH=1 meteor test --driver-package meteortesting:mocha",
"test:ci": "meteor test --once --driver-package meteortesting:mocha",
"test:full": "meteor test --full-app --driver-package meteortesting:mocha"
}
}
测试模式约定
- 普通测试模式会主动加载 .test[s]. 和 .spec[s]. 文件,普通应用代码仅在测试导入时加载。
- 文件名发现会忽略 tests/ 目录。配置了 meteor.testModule 时,改为加载其显式入口模块及其导入。
- --full-app 会主动加载普通应用以及 .app-test[s]. 和 .app-spec[s]. 文件,并设置 Meteor.isAppTest。
- 针对 Meteor 3.5.2 Rspack full-app/TLA 行为、parent-private 发现失败,以及 Meteor 3.6-beta.0 修复的 jQuery 测试框架回归,请阅读特定版本的测试设置。应用版本特定修复前,先检查已解析的包和套件数量。
- 要聚焦现有测试,优先使用受支持的驱动过滤器(如 MOCHA_GREP),先检查 .meteor/versions 中已安装的驱动;.only 仅临时使用。这些只选择已注册的测试,不会阻止其他测试模块被求值。如果加载本身造成干扰,检查 meteor.testModule,并在缩小入口导入前阅读 references/focused-runs.md。恢复所有临时聚焦或导入改动,然后运行正常的受影响套件。从项目读取已配置的入口路径;不要臆造路径、脚本、端口或辅助函数。
- 针对 3.6-beta.3 Rspack 忽略异常或单独的旧版测试入口,使用架构和忽略边界。检查每个所选程序上预期的套件;现代零退出运行并不能证明旧版覆盖。
方法测试(服务器端)
import assert from "node:assert/strict";
import { Meteor } from "meteor/meteor";
import { Items } from "/imports/api/items";
if (Meteor.isServer) {
describe("items.add", function () {
beforeEach(async function () {
await Items.removeAsync({});
});
it("inserts when authed", async function () {
const _id = await Meteor.server.method_handlers["items.add"].apply(
{ userId: "u1" },
[{ title: "x", qty: 1 }],
);
const doc = await Items.findOneAsync(_id);
assert.equal(doc.title, "x");
});
it("rejects when unauthed", async function () {
await assert.rejects(() =>
Meteor.server.method_handlers["items.add"].apply({ userId: null }, [{}]),
);
});
});
}
.apply(context, argsArray) 是文档化形式;Meteor.server.method_handlers[name] 是已注册的方法实现。传入带 userId 的桩 this(需要时还包括 unblock、connection 等)。
发布测试(服务器端)
if (Meteor.isServer) {
describe("items.mine publication", function () {
it("only ships the subscribing user's items", async function () {
await Items.removeAsync({});
await Items.insertAsync({ _id: "a", ownerId: "u1" });
await Items.insertAsync({ _id: "b", ownerId: "u2" });
const cursor = await Meteor.server.publish_handlers["items.mine"]
.apply({ userId: "u1" }, []);
const docs = await cursor.fetchAsync();
assert.deepEqual(docs.map((d) => d._id), ["a"]);
});
});
}
对于使用底层 this.added / this.changed API 的发布,请通过 --full-app 模式用真实 DDP 客户端测试(见下一节)。
始终 await 直接发布处理器。常规处理器立即返回游标;异步处理器返回游标的 Promise。
端到端 DDP 测试(--full-app 模式)
import { DDP } from "meteor/ddp-client";
import { Mongo } from "meteor/mongo";
import { Tracker } from "meteor/tracker";
const conn = DDP.connect(Meteor.absoluteUrl());
const RemoteItems = new Mongo.Collection("items", { connection: conn });
function ready(sub) {
return new Promise((resolve) => {
const computation = Tracker.autorun((c) => {
if (sub.ready()) {
c.stop();
resolve();
}
});
});
}
const sub = conn.subscribe("items.mine");
await ready(sub);
const docs = RemoteItems.find().fetch();
使用 meteor test --full-app --driver-package meteortesting:mocha 运行。
客户端单元测试(Minimongo)
if (Meteor.isClient) {
describe("Items minimongo", function () {
beforeEach(function () { Items.remove({}); });
it("filters by ownerId", function () {
Items.insert({ _id: "a", ownerId: "u1" });
Items.insert({ _id: "b", ownerId: "u2" });
assert.equal(Items.find({ ownerId: "u1" }).count(), 1);
});
});
}
客户端测试在驱动启动的浏览器中运行。设置 TEST_BROWSER_DRIVER=puppeteer 时,浏览器为无头 Chromium,结果回传到服务器控制台。
不要把仅服务器端通过视为完整的客户端覆盖。没有浏览器连接时驱动会打印消息;在 CI 中配置 TEST_BROWSER_DRIVER 并确认客户端测试数量存在。
浏览器 E2E
对普通应用运行 Playwright 或 Cypress,除非测试需要 Meteor 的 app-test 模块。服务器命令必须能从干净克隆运行,使用受跟踪的非机密设置 fixture 或完整记录的测试环境变量。不要指向被忽略的开发者设置文件。
至少覆盖一个完整变更流程和一个被拒绝的服务器操作。基本页面标题检查不会检验异步方法桩、授权或发布就绪状态。
反模式
- 使用 Jest。Jest 不理解 Meteor 的构建系统。使用 meteortesting:mocha。
- Mock Mongo。对干净数据库运行真实驱动。
- 忘记 if (Meteor.isServer) { ... } 守卫。服务器测试在浏览器测试框架中运行会崩溃。
- 跳过 beforeEach 清理。测试会在多次运行间泄漏状态。
- 捕获并记录 beforeEach 失败而不重新抛出。测试随后在未知状态下运行,可能报告误导性断言。
- 对被拒绝的方法只断言数据库后置条件。还应要求 Promise 以预期的 Meteor.Error 拒绝。
- 替换 Meteor.userId 或其他全局变量后不在 afterEach 中恢复。
- 在异步测试代码中使用带回调的 Meteor.call。使用 Meteor.callAsync 或直接解包 method_handlers。
另见
- references/mocha-setup.md
- references/focused-runs.md
- references/ddp-test-helpers.md
- references/eval-cases.md