关于 meteor-testing
meteor-testing 是面向 Meteor 3 开发者的测试指导技能,涵盖测试驱动器选择、服务端与客户端测试,以及 DDP 和浏览器端到端测试。
主要功能
- 01使用 meteortesting:mocha 测试驱动器
- 02测试方法和发布功能
- 03通过 DDP 执行端到端测试
- 04区分单元、集成与浏览器测试
适用场景
- 01验证服务端方法和发布逻辑
- 02测试真实 DDP 客户端交互
- 03为 Meteor 应用设计浏览器端到端测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ meteor/ agent-skills --skill meteor-testing安装后在 Claude Code 中输入/meteor-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ddp-test-helpers.mdeval-cases.mdfocused-runs.mdmeteor-3.5.2-testing.mdmocha-setup.md详细介绍
测试 Meteor 3 应用
meteortesting:mocha 是 Meteor 官方测试驱动程序。它会以测试模式启动应用(除测试文件外,不运行任何应用代码),并在服务器控制台中报告结果。
如果现有测试失败、挂起或不稳定,且无法确定失败层级,请使用 meteor-debugging 隔离原因。当证据指向测试设置、设计、驱动程序行为、测试固件或回归覆盖时,再回到这里。
决策流程
- 现有测试出现未知故障?使用
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 行为、父级 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 客户端进行测试(见下一节)。
务必等待直接发布处理程序完成。常规处理程序会立即返回游标;异步处理程序则会返回一个包含游标的 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,并确认存在客户端测试计数。
浏览器端到端测试
除非测试需要 Meteor 的 app-test 模块,否则请针对常规应用运行 Playwright 或 Cypress。服务器命令必须能在全新克隆的代码库中运行,并使用受版本控制且不含机密的设置样例,或完整记录的测试环境变量。不要将其指向被忽略的开发者设置文件。
至少覆盖一个完整的变更流程和一个被拒绝的服务器操作。 仅检查页面标题无法测试异步方法存根、授权或发布就绪情况。
反模式
- 使用 Jest。Jest 不理解 Meteor 的构建系统。请使用
meteortesting:mocha。 - 模拟 Mongo。请在干净的数据库中运行真实驱动程序。
- 忘记添加
if (Meteor.isServer) { ... }防护。服务器测试在浏览器测试环境中运行时会崩溃。 - 跳过
beforeEach清理。测试会在多次运行之间泄漏状态。 - 捕获并记录
beforeEach失败,却不重新抛出。这样测试会在未知状态下运行,并可能报告误导性的断言。 - 对被拒绝的方法只断言数据库的事后状态。还必须要求 Promise 以预期的
Meteor.Error拒绝。 - 替换
Meteor.userId或其他全局变量,却不在afterEach中恢复。 - 在异步测试代码中使用带回调的
Meteor.call。请使用Meteor.callAsync,或直接展开method_handlers。
另请参阅
references/mocha-setup.mdreferences/focused-runs.mdreferences/ddp-test-helpers.mdreferences/eval-cases.md