现在,我们可以把 supertest 和其他测试框架整合起来了,我选择了 mocha 作为例子,因为它很经典,当你会用 mocha 之后,其他测试框架基本上就难不倒你了。
const co = require('co') const { ObjectId } = require('mongoose').Types const config = require('../config') const UserModel = require('../models/user') const app = require('../app') const request = require('supertest')(app) describe('User API', function (){ // 为每个单元测试初始化数据 // 每个单元测试中可以通过 context 来访问相关的数据 beforeEach(function (done){ co(function* (){ self.user1 = yield UserModel.create({ username: 'user1' }) self.token = jwt.sign({ _id: self.user1._id }, config.jwtSecret, { expiresIn: 3600 }) done() }).catch(err => { console.log('err: ', err) done() }) }) // 正常情况下访问 /user it('should get user info when GET /user with token', function (done){ const self = this request .get('/user') .set('Authorization', self.token) .expect(200) .end((err, res) => { res.body._id.should.equal(self.user1._id) done() }) }) // 非正常情况下访问 /user it('should return 403 when GET /user without token', function (done){ request .get('/user') .expect(403, done) }) // 访问 /users,登录用户和非登录用户都会得到相同的结果,所以不需要区别对待 it('should return user list when GET /users', function (done){ request .get('/users') .expect(200) .end((err, res) => { res.body.should.be.an.Array() done() }) }) // 访问 /users/:userId 也不需要区分登录和非登录状态 it('should return user info when GET /users/:userId', function (done){ const self = this request .get(`/users/${self.user1._id}`) .expect(200) .end((err, res) => { res.body._id.should.equal(self.user1._id) done() }) }) // 访问不存在的用户,我们需要构造一个虚假的用户 id it('should return 404 when GET /users/${non-existent}', function (done){ request .get(`/users/${ObjectId()}`) .expect(404, done) }) // 正常情况下的用户注册不会带上 token it('should return user info when POST /user', function (done){ const username = 'test user' request .post('/users') .send({ username: username }) .expect(200) .end((err, res) => { res.body.username.should.equal(username) done() }) }) // 非法情况下的用户注册,带上了 token 的请求要判断为非法请求 it('should return 400 when POST /user with token', function (done){ const username = 'test user 2' request .post('/users') .set('Authorization', this.token) .send({ username: username }) .expect(400, done) }) // 正常情况下更新用户信息,需要带上 token it('should return 200 when PUT /user with token', function (done){ request .put('/user') .set('Authorization', this.token) .send({ username: 'valid username' }) .expect(200, done) }) // 非法情况下更新用户信息,如缺少 token it('should return 400 when PUT /user without token', function (done){ request .put('/user') .send({ username: 'valid username' }) .expect(400, done) }) }) |
可以看到,为 Restful API 编写单元测试还有一个优点,就是可以轻易区分登录状态和非登录状态。如果要在用户界面中测试这些功能,那么就需要不停地登录和注销,将会是一项累人的工作~
另外,上面的例子中基本都是对返回状态吗进行断言的,你可以按照自己的需要进行断言。
提示
你可以选择自己喜欢的断言库,我这里选择了 should.js,原因是好读。
个人认为 should.js 和其他断言库比起来有个缺点,就是不好写。
value.should.xxx.yyy.zzz 这个形式和 assert.equal(value, expected) 相比不太直观。
另外由于 should.js 是通过扩展 Object.prototype 的原型来实现的,但 null 值是一个例外,它不能访问任何属性。
因此 should.js 在 null 上会失效。
一个变通的办法是 (value === null).should.equal(true) 。
$ npm test User api should get user info when GET /user with token should return 403 when GET /user without token should return user list when GET /users should return user info when GET /users/:userId should return 404 when GET /users/${non-existent} should return user info when POST /user should return 400 when POST /user with token should return 200 when PUT /user with token should return 400 when PUT /user without token |
当我们运行测试时,看到自己编写的测试都通过时,心里都会非常踏实。
而当我们要对项目进行重构时,这些测试用例会帮我们发现重构过程中的问题,减少 Debug 时间,提升重构时的效率。
细节
如何连接测试数据库
在 Node.js 的环境下,我们可以设置环境变量 NODE_ENV=test ,然后通过这个环境变量去连接测试数据库,这样测试数据就不会存在于开发环境下的数据库拉!
// config.js
module.exports = {
development: {},
production: {},
test: {}
}
// app.js
const ENV = process.NODE_ENV || 'development'
const config = require('./config')[ENV]
// connect db by config
如何清空测试数据库
清空数据库这种一次性的工作最好放到 npm scripts 中处理,需要进行清空操作的时候直接运行 npm run resetDB 就可以了。
需要注意的是,编写清空数据库脚本时必须判断环境变量 NODE_ENV ,以免误删 production 环境下的数据。
// resetDB.js const env = process.NODE_ENV || 'development' if (env === 'test' || env === 'development') { // connect db and delete data } else { throw new Error('You can not run this script in production.') } // package.json { "scripts": { "resetDB": "node scripts/resetDB.js" }, // ... } |
何时清空测试环境的数据库
如果是按照上面的原则来生成测试数据的话,测试数据其实可以不用删掉的。
但由于测试数据会占用我们的空间,最好还是把这些测试数据删掉。
那么,清空测试数据库这个操作在测试前执行好,还是测试后执行好?
我个人倾向于测试前删除,因为有时候我们需要进入数据库,查看测试数据的正确性。
如果在测试后清空测试数据库的话,我们就没办法访问到测试数据了。
{ "scripts": { "resetDB": "node scripts/resetDB.js", "test": "NODE_ENV=test npm run resetDB && mocha --harmony" }, // ... } |