meteor-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing Meteor 3 apps

Meteor 3 应用测试

meteortesting:mocha
is the canonical Meteor test driver. It boots the app in test mode (no app code runs except the test files) and reports results in the server console.
If an existing test fails, hangs, or flakes and the failing layer is unknown, use
meteor-debugging
to isolate the cause. Return here when evidence points to test setup, design, driver behavior, fixtures, or regression coverage.
meteortesting:mocha
是Meteor官方推荐的测试驱动程序。它会以测试模式启动应用(除测试文件外,其他应用代码不会运行),并在服务器控制台中报告测试结果。
如果现有测试出现失败、挂起或不稳定的情况,且未知失败层级,请使用
meteor-debugging
来定位原因。当证据指向测试设置、设计、驱动程序行为、测试夹具或回归覆盖范围时,再回到此技能。

Decision flow

决策流程

  1. Unknown failure in an existing test? Use
    meteor-debugging
    to classify application, data, harness, browser, environment, or shared-state causes.
  2. Pure logic, no Meteor APIs? Plain Mocha or any test runner; nothing Meteor-specific.
  3. Method or publication? Integration test it with
    meteortesting:mocha
    using
    Meteor.server.method_handlers[name].apply(ctx, args)
    or
    publish_handlers
    .
  4. Need to drive a real DDP client (cross-process or end-to-end DDP semantics)? Use
    --full-app
    mode and
    DDP.connect
    .
  5. UI flow / browser interaction? Start the normal app with deterministic test settings and run Playwright or Cypress against it. Use
    meteor test --full-app
    only when the browser flow must load app-test modules.
  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进行测试。仅当浏览器流程必须加载应用测试模块时,才使用
    meteor test --full-app

Setup

设置

bash
meteor add meteortesting:mocha
meteor npm install --save-dev @types/mocha
json
// 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-mode conventions:
  • Normal test mode eagerly loads
    *.test[s].*
    and
    *.spec[s].*
    files, while ordinary application code loads only when imported by a test.
  • Filename discovery ignores
    tests/
    directories. A configured
    meteor.testModule
    instead loads its explicit entry module and imports.
  • --full-app
    eagerly loads the normal app plus
    *.app-test[s].*
    and
    *.app-spec[s].*
    files, and sets
    Meteor.isAppTest
    .
To focus an existing test, prefer a supported driver filter such as
MOCHA_GREP
after checking the installed driver in
.meteor/versions
; use
.only
only temporarily. These select registered tests but do not prevent other test modules from evaluating. If loading itself causes interference, inspect
meteor.testModule
and read
references/focused-runs.md
before narrowing entrypoint imports. Restore every temporary focus or import change, then run the normal affected suite. Read the configured entrypoint paths from the project; do not invent paths, scripts, ports, or helpers.
bash
meteor add meteortesting:mocha
meteor npm install --save-dev @types/mocha
json
// 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/versions
中已安装的驱动程序后,使用支持的驱动程序过滤器(如
MOCHA_GREP
);仅临时使用
.only
。这些方式会选择已注册的测试,但无法阻止其他测试模块执行。如果加载过程本身造成干扰,请先检查
meteor.testModule
并阅读
references/focused-runs.md
,再缩小入口导入范围。恢复所有临时聚焦或导入更改后,运行受影响的常规测试套件。从项目中读取已配置的入口路径,不要自行创建路径、脚本、端口或辅助工具。

Method test (server-side)

方法测试(服务端)

javascript
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)
is the documented form;
Meteor.server.method_handlers[name]
is the registered method implementation. Pass a stub
this
with
userId
(and
unblock
,
connection
, etc. when needed).
javascript
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
等属性)。

Publication test (server-side)

发布测试(服务端)

javascript
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"]);
    });
  });
}
For publications using the low-level
this.added
/
this.changed
API, test with a real DDP client via
--full-app
mode (next section).
Always await the direct publish handler. Conventional handlers return the cursor immediately; async handlers return a Promise of the cursor.
javascript
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。

End-to-end DDP test (
--full-app
mode)

端到端DDP测试(
--full-app
模式)

javascript
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();
Run with
meteor test --full-app --driver-package meteortesting:mocha
.
javascript
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
运行。

Client unit test (Minimongo)

客户端单元测试(Minimongo)

javascript
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);
    });
  });
}
Client tests run in the browser the driver spawns. With
TEST_BROWSER_DRIVER=puppeteer
, the browser is headless Chromium and results flow back to the server console.
Do not treat a server-only green run as complete client coverage. The driver prints a message when no browser is connected; configure
TEST_BROWSER_DRIVER
in CI and confirm client test counts are present.
javascript
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
并确认客户端测试计数存在。

Browser E2E

浏览器端到端测试

Run Playwright or Cypress against the normal application unless the test needs Meteor's app-test modules. The server command must work from a clean clone with a tracked, nonsecret settings fixture or fully documented test environment variables. Do not point it at an ignored developer settings file.
Cover at least one complete mutation flow and one rejected server action. Basic page-title checks do not exercise async method stubs, authorization, or publication readiness.
除非测试需要Meteor的应用测试模块,否则请针对常规应用运行Playwright或Cypress。服务器命令必须能在干净的克隆项目中运行,使用已跟踪的非保密设置夹具或文档齐全的测试环境变量。不要指向被忽略的开发者设置文件。 至少覆盖一个完整的变更流程和一个被拒绝的服务器操作。基础的页面标题检查无法验证异步方法存根、授权或发布就绪状态。

Anti-patterns

反模式

  • Reach for Jest. Jest does not understand Meteor's build system. Use
    meteortesting:mocha
    .
  • Mock Mongo. Run the real driver against a clean DB.
  • Forget
    if (Meteor.isServer) { ... }
    guards. Server tests crash if they run in the browser harness.
  • Skip
    beforeEach
    cleanup. Tests leak state across runs.
  • Catch and log a
    beforeEach
    failure without rethrowing. The test then runs against unknown state and can report a misleading assertion.
  • Assert only the database postcondition for a rejected method. Also require the Promise to reject with the expected
    Meteor.Error
    .
  • Replace
    Meteor.userId
    or another global without restoring it in
    afterEach
    .
  • Use
    Meteor.call
    with callbacks in async test code. Use
    Meteor.callAsync
    or unwrap
    method_handlers
    directly.
  • 使用Jest。Jest无法理解Meteor的构建系统,请使用
    meteortesting:mocha
  • 模拟Mongo。请针对干净的数据库运行真实驱动程序。
  • 忘记添加
    if (Meteor.isServer) { ... }
    防护。服务端测试若在浏览器工具中运行会崩溃。
  • 跳过
    beforeEach
    清理步骤。测试会在多次运行间泄露状态。
  • 捕获并记录
    beforeEach
    的失败但不重新抛出异常。测试会在未知状态下运行,可能产生误导性的断言结果。
  • 仅断言被拒绝方法的数据库后置条件。还需验证Promise是否抛出了预期的
    Meteor.Error
  • 替换
    Meteor.userId
    或其他全局变量但未在
    afterEach
    中恢复。
  • 在异步测试代码中使用带回调的
    Meteor.call
    。请使用
    Meteor.callAsync
    或直接调用
    method_handlers

See also

另请参阅

  • references/mocha-setup.md
  • references/focused-runs.md
  • references/ddp-test-helpers.md
  • references/eval-cases.md
  • references/mocha-setup.md
  • references/focused-runs.md
  • references/ddp-test-helpers.md
  • references/eval-cases.md