// 根據(jù) token 獲取用戶信息,必須登錄
router.get('/user', jwt, user.getSelf)
// 獲取用戶列表,無需登錄
router.get('/users', user.getList)
// 獲取指定用戶信息,無需登錄
router.get('/users/:userId', user.get)
// 創(chuàng)建新用戶(用戶注冊),無需登錄
router.post('/users', user.create)
// 更新用戶信息,必須登錄
router.put('/user', jwt, user.update)

那么我們應(yīng)該怎樣為這些 Restful API 編寫單元測試呢?


  最基本流程是:
  · 為 app 創(chuàng)建 http 服務(wù)器
  · 對各個 API 發(fā)出請求
  · 對響應(yīng)內(nèi)容進(jìn)行斷言
  幸運(yùn)的是,社區(qū)里已經(jīng)有相應(yīng)的工具讓我們可以方便管理這個流程,這個工具就是 —— supertest 。
  它提供了非常靈活的 API,足以幫助我們測試 Restful API 了。
  基本用法如下:
  

const app = require('../app')
  const request = require('supertest')(app)
  request
  .get('/users')
  .expect(200)
  .end((err, res) => {
  res.body.should.be.an.Array()
  })


  提示
  如果你遇到了 TypeError: app.address is not a function , 請嘗試一下以下方法:
  const request = require(‘supertest’).agent(app.listen())

現(xiàn)在,我們可以把 supertest 和其他測試框架整合起來了,我選擇了 mocha 作為例子,因為它很經(jīng)典,當(dāng)你會用 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 (){


// 為每個單元測試初始化數(shù)據(jù)
// 每個單元測試中可以通過 context 來訪問相關(guān)的數(shù)據(jù)

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,登錄用戶和非登錄用戶都會得到相同的結(jié)果,所以不需要區(qū)別對待
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 也不需要區(qū)分登錄和非登錄狀態(tài)
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() }) }) // 訪問不存在的用戶,我們需要構(gòu)造一個虛假的用戶 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 編寫單元測試還有一個優(yōu)點,就是可以輕易區(qū)分登錄狀態(tài)和非登錄狀態(tài)。如果要在用戶界面中測試這些功能,那么就需要不停地登錄和注銷,將會是一項累人的工作~
  另外,上面的例子中基本都是對返回狀態(tài)嗎進(jìn)行斷言的,你可以按照自己的需要進(jìn)行斷言。
  提示
  你可以選擇自己喜歡的斷言庫,我這里選擇了 should.js,原因是好讀。
  個人認(rèn)為 should.js 和其他斷言庫比起來有個缺點,就是不好寫。
  value.should.xxx.yyy.zzz 這個形式和 assert.equal(value, expected) 相比不太直觀。
  另外由于 should.js 是通過擴(kuò)展 Object.prototype 的原型來實現(xiàn)的,但 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


  當(dāng)我們運(yùn)行測試時,看到自己編寫的測試都通過時,心里都會非常踏實。
  而當(dāng)我們要對項目進(jìn)行重構(gòu)時,這些測試用例會幫我們發(fā)現(xiàn)重構(gòu)過程中的問題,減少 Debug 時間,提升重構(gòu)時的效率。

細(xì)節(jié)如何連接測試數(shù)據(jù)庫


  在 Node.js 的環(huán)境下,我們可以設(shè)置環(huán)境變量 NODE_ENV=test ,然后通過這個環(huán)境變量去連接測試數(shù)據(jù)庫,這樣測試數(shù)據(jù)就不會存在于開發(fā)環(huán)境下的數(shù)據(jù)庫拉!
  

// config.js
  module.exports = {
  development: {},
  production: {},
  test: {}
  }
  // app.js
  const ENV = process.NODE_ENV || 'development'
  const config = require('./config')[ENV]
  // connect db by config


  如何清空測試數(shù)據(jù)庫
  清空數(shù)據(jù)庫這種一次性的工作最好放到 npm scripts 中處理,需要進(jìn)行清空操作的時候直接運(yùn)行 npm run resetDB 就可以了。
  需要注意的是,編寫清空數(shù)據(jù)庫腳本時必須判斷環(huán)境變量 NODE_ENV ,以免誤刪 production 環(huán)境下的數(shù)據(jù)。

// 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"
},
// ...
}

何時清空測試環(huán)境的數(shù)據(jù)庫


  如果是按照上面的原則來生成測試數(shù)據(jù)的話,測試數(shù)據(jù)其實可以不用刪掉的。
  但由于測試數(shù)據(jù)會占用我們的空間,最好還是把這些測試數(shù)據(jù)刪掉。
  那么,清空測試數(shù)據(jù)庫這個操作在測試前執(zhí)行好,還是測試后執(zhí)行好?
  我個人傾向于測試前刪除,因為有時候我們需要進(jìn)入數(shù)據(jù)庫,查看測試數(shù)據(jù)的正確性。
  如果在測試后清空測試數(shù)據(jù)庫的話,我們就沒辦法訪問到測試數(shù)據(jù)了。

{
"scripts": {
"resetDB": "node scripts/resetDB.js",
"test": "NODE_ENV=test npm run resetDB && mocha --harmony"
},
// ...
}

本文章轉(zhuǎn)載微信公眾號@金陽光測試

上一篇:

用ASP.NET Core 給你的API接口打造一個自定義認(rèn)證授體系

下一篇:

保護(hù)JavaScript客戶端到API服務(wù)的通信
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費(fèi)

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費(fèi)