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
模块,其中包含几个使用 describe
、it
和 assert
样式的示例测试,这些样式由 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
中导入 assert
(import { 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
此命令适用于在 Travis CI 或 CircleCI 等持续集成 (CI) 环境中运行,因为它仅运行您的服务器端测试,然后如果所有测试都通过则退出代码为 0
。
如果您希望在开发应用程序时运行测试(并在开发服务器重新启动时重新运行它们),请考虑使用 meteor npm run test-app
,它等效于
TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha
这与最后一个命令几乎相同,只是它像往常一样加载您的应用程序代码(由于 --full-app
),允许您在运行客户端和服务器测试时与浏览器中的应用程序进行交互。
您可以使用 Meteor 测试做更多的事情!您可以在 Meteor 指南中阅读更多相关信息关于测试的文章。
回顾:您可以在此步骤结束时查看您的代码应如何编写此处。
在下一步中,我们将把您的应用程序部署到 Galaxy,这是 Meteor 应用程序的最佳托管服务,由 Meteor 背后的同一个团队开发。