写测试¶
每当我们生成一个钩子或服务时, 生成器也会设置一个基本的 Mocha test, 我们可以使用它来为它实现单元测试. 在本章中, 我们将为我们的 处理数据 和 创建服务 的集成测试实现单元测试.
我们可以运行 code Linter 和Mocha测试
npm test
这将最初失败, 因为我们在钩子中实现了标准测试未涵盖的功能. 所以让我们先通过.
单元测试挂钩¶
测试单个钩子的最好方法是设置一个虚拟Feathers应用程序, 其中包含一些返回我们期望的数据并可以测试的服务, 然后注册钩子并进行实际的服务调用以验证它们返回我们期望的内容.
我们创建的第一个钩子是用于处理新消息. 对于这个钩子, 我们可以创建一个 messages 虚拟自定义 服务, 只返回来自 create 服务方法的相同数据. 假装我们是经过身份验证的用户, 我们必须通过 params.user. 对于此测试, 这可以是一个带有 _id 的简单JavaScript对象.
将 test/hooks/process-messages.test.js 更新为以下内容:
const assert = require('assert');
const feathers = require('@feathersjs/feathers');
const processMessage = require('../../src/hooks/process-message');
describe('\'process-message\' hook', () => {
let app;
beforeEach(() => {
// Create a new plain Feathers application
app = feathers();
// Register a dummy custom service that just return the
// message data back
app.use('/messages', {
async create(data) {
return data;
}
});
// Register the `processMessage` hook on that service
app.service('messages').hooks({
before: {
create: processMessage()
}
});
});
it('processes the message as expected', async () => {
// A user stub with just an `_id`
const user = { _id: 'test' };
// The service method call `params`
const params = { user };
// Create a new message with params that contains our user
const message = await app.service('messages').create({
text: 'Hi there',
additional: 'should be removed'
}, params);
assert.equal(message.text, 'Hi there');
// `userId` was set
assert.equal(message.userId, 'test');
// `additional` property has been removed
assert.ok(!message.additional);
});
});
我们可以采用类似的方法来测试 test/hooks/gravatar.test.js 中的 gravatar 钩子. :
const assert = require('assert');
const feathers = require('@feathersjs/feathers');
const gravatar = require('../../src/hooks/gravatar');
describe('\'gravatar\' hook', () => {
let app;
beforeEach(() => {
app = feathers();
// A dummy users service for testing
app.use('/users', {
async create(data) {
return data;
}
});
// Add the hook to the dummy service
app.service('users').hooks({
before: {
create: gravatar()
}
});
});
it('creates a gravatar link from the users email', async () => {
const user = await app.service('users').create({
email: 'test@example.com'
});
assert.deepEqual(user, {
email: 'test@example.com',
avatar: 'https://s.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=60'
});
});
});
在上面的测试中, 我们创建了一个虚拟服务. 但有时, 我们需要完整的Feathers服务功能. feathers-memory 是一个有用的 数据库, 支持Feathers查询语法(和分页)但不支持需要数据库服务器. 我们可以将它安装为开发依赖项:
npm install feathers-memory --save-dev
让我们用它来测试 populateUser 钩子, 将 test/hooks/populate-user.test.js 更新为:
const assert = require('assert');
const feathers = require('@feathersjs/feathers');
const memory = require('feathers-memory');
const populateUser = require('../../src/hooks/populate-user');
describe('\'populate-user\' hook', () => {
let app, user;
beforeEach(async () => {
// Database adapter pagination options
const options = {
paginate: {
default: 10,
max: 25
}
};
app = feathers();
// Register `users` and `messages` service in-memory
app.use('/users', memory(options));
app.use('/messages', memory(options));
// Add the hook to the dummy service
app.service('messages').hooks({
after: populateUser()
});
// Create a new user we can use to test with
user = await app.service('users').create({
email: 'test@user.com'
});
});
it('populates a new message with the user', async () => {
const message = await app.service('messages').create({
text: 'A test message',
// Set `userId` manually (usually done by `process-message` hook)
userId: user.id
});
// Make sure that user got added to the returned message
assert.deepEqual(message.user, user);
});
});
如果我们现在运行:
npm test
我们所有的测试都应该通过好极了!
注解
运行测试时会打印一些错误堆栈. 这是正常的, 它们是运行404(未找到)错误测试时的日志条目.
测试数据库设置¶
在测试数据库功能时, 我们希望确保测试使用不同的数据库. 我们可以通过在 config/test.json 中创建一个具有以下内容的新环境配置来实现这一点:
{
"nedb": "../test/data"
}
当 NODE_ENV 设置为 test 时, 这将设置NeDB数据库使用 test/data 作为基目录而不是 data/. 对于其他数据库的连接字符串也可以完成同样的事情.
我们还希望确保在每次测试运行之前清理数据库. 为了在跨平台实现这一点, 首先运行:
npm install shx --save-dev
现在我们可以将 package.json 的 script 部分更新为以下内容:
"scripts": {
"test": "npm run eslint && npm run mocha",
"eslint": "eslint src/. test/. --config .eslintrc.json",
"start": "node src/",
"clean": "shx rm -rf test/data/",
"mocha": "npm run clean && NODE_ENV=test mocha test/ --recursive --exit"
}
在Windows上, mocha 应该是这样的:
npm run clean & SET NODE_ENV=test& mocha test/ --recursive --exit
这将确保在每次测试运行之前删除 test/data 文件夹并正确设置 NODE_ENV.
测试服务¶
为了测试实际的 messages 和 users 服务(所有挂钩连线), 我们可以使用任何REST API测试工具发出请求并验证它们是否返回正确的响应.
但是有一种更快, 更简单和完整的方法. 由于Feathers已经提供(和测试)了我们自己的钩子和服务之上的所有东西, 我们可以直接要求 服务, 并通过设置 params.user 来 “fake” 认证 如上面的钩子测试中所示.
默认情况下, 生成器创建服务测试文件, 例如, test/services/users.test.js, 只测试服务是否存在, 如下所示:
const assert = require('assert');
const app = require('../../src/app');
describe('\'users\' service', () => {
it('registered the service', () => {
const service = app.service('users');
assert.ok(service, 'Registered the service');
});
});
然后我们可以添加使用该服务的类似测试. 以下是一个更新的 test/services/users.test.js, 它增加了两个测试. 第一个验证是否可以创建用户, 设置gravatar并加密密码. 第二个验证密码未发送到外部请求:
const assert = require('assert');
const app = require('../../src/app');
describe('\'users\' service', () => {
it('registered the service', () => {
const service = app.service('users');
assert.ok(service, 'Registered the service');
});
it('creates a user, encrypts password and adds gravatar', async () => {
const user = await app.service('users').create({
email: 'test@example.com',
password: 'secret'
});
// Verify Gravatar has been set as we'd expect
assert.equal(user.avatar, 'https://s.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=60');
// Makes sure the password got encrypted
assert.ok(user.password !== 'secret');
});
it('removes password for external requests', async () => {
// Setting `provider` indicates an external request
const params = { provider: 'rest' };
const user = await app.service('users').create({
email: 'test2@example.com',
password: 'secret'
}, params);
// Make sure password has been removed
assert.ok(!user.password);
});
});
我们对 test/services/messages.test.js 采取了类似的方法. 我们从 users 服务创建一个特定于测试的用户. 然后我们在创建新消息时将其作为 params.user 传递, 并验证该消息的内容:
const assert = require('assert');
const app = require('../../src/app');
describe('\'messages\' service', () => {
it('registered the service', () => {
const service = app.service('messages');
assert.ok(service, 'Registered the service');
});
it('creates and processes message, adds user information', async () => {
// Create a new user we can use for testing
const user = await app.service('users').create({
email: 'messagetest@example.com',
password: 'supersecret'
});
// The messages service call params (with the user we just created)
const params = { user };
const message = await app.service('messages').create({
text: 'a test',
additional: 'should be removed'
}, params);
assert.equal(message.text, 'a test');
// `userId` should be set to passed users it
assert.equal(message.userId, user._id);
// Additional property has been removed
assert.ok(!message.additional);
// `user` has been populated
assert.deepEqual(message.user, user);
});
});
再次运行 npm test, 验证我们所有钩子的测试以及新的服务测试是否通过.
Client/server 测试¶
您可以编写测试来启动应用程序的服务器, 以及测试可用于调用服务器的Feathers客户端. 此类测试可能会暴露客户端与服务器之间交互的错误. 它们还可用于测试来自客户端的请求的身份验证. 将其安装为开发依赖项:
npm install @feathersjs/client --save-dev
从上面测试 test/services/users.test.js 在服务器上运行. 我们将其转换为以下 tests/services/client-users.test.js, 因此测试在客户端而不是服务器上运行. 这也会导致客户端身份验证被测试.
const assert = require('assert');
const feathersClient = require('@feathersjs/client');
const io = require('socket.io-client');
const app = require('../../src/app');
const host = app.get('host');
const port = app.get('port');
const email = 'login@example.com';
const password = 'login';
describe('\'users\' service - client', function () {
this.timeout(10000);
let server;
let client;
before(async () => {
await app.service('users').create({ email, password });
server = app.listen(port);
server.on('listening', async () => {
// eslint-disable-next-line no-console
console.log('Feathers application started on http://%s:%d', host, port);
});
client = await makeClient(host, port, email, password);
});
after(() => {
client.logout();
server.close();
});
describe('Run tests using client and server', () => {
it('registered the service', () => {
const service = client.service('users');
assert.ok(service, 'Registered the service');
});
it('creates a user, encrypts password and adds gravatar', async () => {
const user = await client.service('users').create({
email: 'testclient@example.com',
password: 'secret'
});
// Verify Gravatar has been set to what we'd expect
assert.equal(user.avatar, 'https://s.gravatar.com/avatar/1b9c869fa7a93e59463c31a377fe0cf6?s=60');
// Makes sure the password got encrypted
assert.ok(user.password !== 'secret');
});
it('removes password for external requests', async () => {
// Setting `provider` indicates an external request
const params = { provider: 'rest' };
const user = await client.service('users').create({
email: 'testclient2@example.com',
password: 'secret'
}, params);
// Make sure password has been removed
assert.ok(!user.password);
});
});
});
async function makeClient(host, port, email, password) {
const client = feathersClient();
const socket = io(`http://${host}:${port}`, {
transports: ['websocket'], forceNew: true, reconnection: false, extraHeaders: {}
});
client.configure(feathersClient.socketio(socket));
client.configure(feathersClient.authentication({
storage: localStorage()
}));
await client.authenticate({
strategy: 'local',
email,
password,
});
return client;
}
function localStorage () {
const store = {};
return {
setItem (key, value) {
store[key] = value;
},
getItem (key) {
return store[key];
},
removeItem (key) {
delete store[key];
}
};
}
我们首先在 server 上调用以创建新用户. 然后, 我们为我们的应用程序启动服务器. 最后调用函数 makeClient 来创建Feathers客户端并使用新创建的用户对其进行身份验证.
各个测试保持不变, 除了服务调用现在在客户端(client.service(...).create)而不是在服务器上进行(app.service(...).create).
describe('Run tests using client and server', 语句停止为每个测试创建一个新的服务器和客户端.这导致测试模块运行明显更快, 尽管测试现在暴露于潜在的迭代.您可以删除该语句以将测试彼此隔离.
代码覆盖率¶
代码覆盖率是一种很好的方式, 可以了解我们在测试期间实际执行了多少代码. 使用 Istanbul 我们可以轻松添加它:
npm install nyc --save-dev
现在我们必须更新 package.json 的 script 部分:
"scripts": {
"test": "npm run eslint && npm run coverage",
"coverage": "npm run clean && NODE_ENV=test nyc mocha",
"eslint": "eslint src/. test/. --config .eslintrc.json --fix",
"start": "node src/",
"clean": "shx rm -rf test/data/",
"mocha": "npm run clean && NODE_ENV=test mocha test/ --recursive --exit"
},
在Windows上, coverage 命令如下所示:
npm run clean & SET NODE_ENV=test& nyc mocha
现在跑:
npm test
这将打印出一些额外的覆盖信息.
更改默认测试目录¶
要更改默认测试目录, 请在项目的 package.json 文件中指定所需的目录:
{
"directories": {
"test": "server/test/"
}
}
另外, 不要忘记更新 package.json 文件中的mocha脚本:
"scripts": {
"mocha": "mocha server/test/ --recursive --exit"
}