mcpField
M
meteor-testing

meteor-testing

github.com/meteor测试与质量10285 次安装5 天前更新
代码仓库

关于 meteor-testing

meteor-testing 是面向 Meteor 3 开发者的测试指导技能,涵盖测试驱动器选择、服务端与客户端测试,以及 DDP 和浏览器端到端测试。

主要功能

  1. 01使用 meteortesting:mocha 测试驱动器
  2. 02测试方法和发布功能
  3. 03通过 DDP 执行端到端测试
  4. 04区分单元、集成与浏览器测试

适用场景

  1. 01验证服务端方法和发布逻辑
  2. 02测试真实 DDP 客户端交互
  3. 03为 Meteor 应用设计浏览器端到端测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/meteor/agent-skills --skill meteor-testing

安装后在 Claude Code 中输入/meteor-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ddp-test-helpers.md
eval-cases.md
focused-runs.md
meteor-3.5.2-testing.md
mocha-setup.md

详细介绍

测试 Meteor 3 应用

meteortesting:mocha 是 Meteor 官方测试驱动程序。它会以测试模式启动应用(除测试文件外,不运行任何应用代码),并在服务器控制台中报告结果。

如果现有测试失败、挂起或不稳定,且无法确定失败层级,请使用 meteor-debugging 隔离原因。当证据指向测试设置、设计、驱动程序行为、测试固件或回归覆盖时,再回到这里。

决策流程

  1. 现有测试出现未知故障?使用 meteor-debugging 判断原因属于应用、数据、测试框架、浏览器、环境还是共享状态。
  2. 纯逻辑,不使用 Meteor API?使用原生 Mocha 或任意测试运行器;无需 Meteor 专用功能。
  3. 方法或发布?使用 meteortesting:mocha 对其进行集成测试,通过 Meteor.server.method_handlers[name].apply(ctx, args) 或 publish_handlers 进行测试。
  4. 需要驱动真实 DDP 客户端(跨进程或端到端 DDP 语义)?使用 --full-app 模式和 DDP.connect。
  5. 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.md
  • references/focused-runs.md
  • references/ddp-test-helpers.md
  • references/eval-cases.md

评论

支持 Markdown