11: 测试

现在我们已经为我们的应用程序创建了一些功能,让我们添加一个测试来确保我们没有发生回归,并且它按我们预期的方式工作。

我们将编写一个测试,该测试执行我们其中一个方法并验证它是否正确工作。

11.1: 安装依赖项

我们将为 Mocha JavaScript 测试框架添加一个测试驱动程序,以及一个测试断言库。

meteor add meteortesting:mocha
meteor npm install --save-dev chai

我们现在可以通过运行 meteor test 并指定一个测试驱动程序包来以“测试模式”运行我们的应用程序(您需要停止常规应用程序的运行,或使用 –port XYZ 指定备用端口)。

TEST_WATCH=1 meteor test --driver-package meteortesting:mocha

它应该输出类似以下内容

simple-todos-react
  ✓ package.json has correct name
  ✓ server is not client

2 passing (10ms)

这两个测试从哪里来?每个新的 Meteor 应用程序都包含一个 tests/main.js 模块,其中包含几个使用 describeitassert 样式的示例测试,这些样式由 Mocha 等测试框架推广。

Meteor Mocha 集成由社区维护,您可以阅读更多这里的信息。

当您使用这些选项运行时,您也可以在浏览器中的应用程序 URL 中看到测试结果。

11.2: 搭建测试

但是,如果您希望将测试拆分到多个模块中,您也可以这样做。添加一个名为 imports/api/tasksMethods.tests.js 的新测试模块。

imports/api/tasksMethods.tests.js

import { Meteor } from 'meteor/meteor';

if (Meteor.isServer) {
  describe('Tasks', () => {
    describe('methods', () => {
      it('can delete owned task', () => {});
    });
  });
}

并在 tests/main.js 中导入它,例如 import '/imports/api/tasksMethods.tests.js'; 并从该文件中删除所有其他内容,因为我们不需要这些测试。

tests/main.js

import '/imports/api/tasksMethods.tests.js';

11.3: 准备数据库

在任何测试中,您都需要确保数据库在我们开始之前处于我们期望的状态。您可以轻松地使用 Mocha 的 beforeEach 结构来做到这一点。

imports/api/tasksMethods.tests.js

import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { TasksCollection } from '/imports/db/TasksCollection';

if (Meteor.isServer) {
  describe('Tasks', () => {
    describe('methods', () => {
      const userId = Random.id();
      let taskId;

      beforeEach(() => {
        TasksCollection.remove({});
        taskId = TasksCollection.insert({
          text: 'Test Task',
          createdAt: new Date(),
          userId,
        });
      });
    });
  });
}

在这里,您正在创建一个与随机 userId 关联的单个任务,该 userId 在每次测试运行时都将不同。

11.4: 测试任务移除

现在您可以编写测试来调用 tasks.remove 方法作为该用户并验证任务是否已删除,因为您将测试一个方法,并且我们希望模拟已认证的用户,您可以安装此实用程序包以使您的生活更轻松。

meteor add quave:testing

imports/api/tasksMethods.tests.js

import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { mockMethodCall } from 'meteor/quave:testing';
import { assert } from 'chai';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';

if (Meteor.isServer) {
  describe('Tasks', () => {
    describe('methods', () => {
      const userId = Random.id();
      let taskId;

      beforeEach(() => {
        TasksCollection.remove({});
        taskId = TasksCollection.insert({
          text: 'Test Task',
          createdAt: new Date(),
          userId,
        });
      });

      it('can delete owned task', () => {
        mockMethodCall('tasks.remove', taskId, { context: { userId } });

        assert.equal(TasksCollection.find().count(), 0);
      });
    });
  });
}

请记住从 chai 中导入 assertimport { assert } from 'chai';)。

11.5: 更多测试

您可以添加任意数量的测试,下面您可以找到一些其他测试,这些测试可以帮助您获得更多关于测试内容和方法的想法。

imports/api/tasksMethods.tests.js

import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { mockMethodCall } from 'meteor/quave:testing';
import { assert } from 'chai';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';

if (Meteor.isServer) {
  describe('Tasks', () => {
    describe('methods', () => {
      const userId = Random.id();
      let taskId;

      beforeEach(() => {
        TasksCollection.remove({});
        taskId = TasksCollection.insert({
          text: 'Test Task',
          createdAt: new Date(),
          userId,
        });
      });

      it('can delete owned task', () => {
        mockMethodCall('tasks.remove', taskId, { context: { userId } });

        assert.equal(TasksCollection.find().count(), 0);
      });

      it(`can't delete task without an user authenticated`, () => {
        const fn = () => mockMethodCall('tasks.remove', taskId);
        assert.throw(fn, /Not authorized/);
        assert.equal(TasksCollection.find().count(), 1);
      });

      it(`can't delete task from another owner`, () => {
        const fn = () =>
          mockMethodCall('tasks.remove', taskId, {
            context: { userId: 'somebody-else-id' },
          });
        assert.throw(fn, /Access denied/);
        assert.equal(TasksCollection.find().count(), 1);
      });

      it('can change the status of a task', () => {
        const originalTask = TasksCollection.findOne(taskId);
        mockMethodCall('tasks.setIsChecked', taskId, !originalTask.isChecked, {
          context: { userId },
        });

        const updatedTask = TasksCollection.findOne(taskId);
        assert.notEqual(updatedTask.isChecked, originalTask.isChecked);
      });

      it('can insert new tasks', () => {
        const text = 'New Task';
        mockMethodCall('tasks.insert', text, {
          context: { userId },
        });

        const tasks = TasksCollection.find({}).fetch();
        assert.equal(tasks.length, 2);
        assert.isTrue(tasks.some(task => task.text === text));
      });
    });
  });
}

如果您再次运行测试命令或在之前以监视模式运行它,您应该会看到以下输出。

Tasks
  methods
    ✓ can delete owned task
    ✓ can't delete task without an user authenticated
    ✓ can't delete task from another owner
    ✓ can change the status of a task
    ✓ can insert new tasks

5 passing (70ms)

为了更容易键入测试命令,您可能希望在 package.json 文件的 scripts 部分添加一个简写。

事实上,新的 Meteor 应用程序附带了一些预配置的 npm 脚本,您可以随意使用或修改它们。

标准 meteor npm test 命令运行以下命令。

meteor test --once --driver-package meteortesting:mocha

此命令适用于在持续集成 (CI) 环境(如 Travis CICircleCI)中运行,因为它仅运行您的服务器端测试,然后如果所有测试都通过则退出代码为 0

如果您希望在开发应用程序时运行测试(并在开发服务器重新启动时重新运行测试),请考虑使用 meteor npm run test-app,它等效于。

TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha

这与之前的命令几乎相同,除了它还像往常一样加载您的应用程序代码(由于 --full-app),允许您在运行客户端和服务器测试的同时与浏览器中的应用程序进行交互。

您可以使用 Meteor 测试做更多的事情!您可以在 Meteor 指南关于测试的文章中阅读更多相关信息。

回顾:您可以查看此步骤结束时代码应有的样子这里

在下一步中,我们将把您的应用程序部署到 Galaxy,这是 Meteor 应用程序的最佳托管服务,由 Meteor 背后的同一个团队开发。

在 GitHub 上编辑
// 搜索框