现在,我们可以把 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"
},
// ...
}