安全的關(guān)鍵.png)
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶管理restful api
// 根據(jù) token 獲取用戶信息,必須登錄
router.get('/user', jwt, user.getSelf)
// 獲取用戶列表,無(wú)需登錄
router.get('/users', user.getList)
// 獲取指定用戶信息,無(wú)需登錄
router.get('/users/:userId', user.get)
// 創(chuàng)建新用戶(用戶注冊(cè)),無(wú)需登錄
router.post('/users', user.create)
// 更新用戶信息,必須登錄
router.put('/user', jwt, user.update)
最基本流程是:
· 為 app 創(chuàng)建 http 服務(wù)器
· 對(duì)各個(gè) API 發(fā)出請(qǐng)求
· 對(duì)響應(yīng)內(nèi)容進(jìn)行斷言
幸運(yùn)的是,社區(qū)里已經(jīng)有相應(yīng)的工具讓我們可以方便管理這個(gè)流程,這個(gè)工具就是 —— supertest 。
它提供了非常靈活的 API,足以幫助我們測(cè)試 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 , 請(qǐng)嘗試一下以下方法:
const request = require(‘supertest’).agent(app.listen())
現(xiàn)在,我們可以把 supertest 和其他測(cè)試框架整合起來(lái)了,我選擇了 mocha 作為例子,因?yàn)樗芙?jīng)典,當(dāng)你會(huì)用 mocha 之后,其他測(cè)試框架基本上就難不倒你了。
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 (){
// 為每個(gè)單元測(cè)試初始化數(shù)據(jù)
// 每個(gè)單元測(cè)試中可以通過(guò) context 來(lái)訪問(wèn)相關(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()
})
})
// 正常情況下訪問(wèn) /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()
})
})
// 非正常情況下訪問(wèn) /user
it('should return 403 when GET /user without token', function (done){
request
.get('/user')
.expect(403, done)
})
// 訪問(wèn) /users,登錄用戶和非登錄用戶都會(huì)得到相同的結(jié)果,所以不需要區(qū)別對(duì)待
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()
})
})
// 訪問(wèn) /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()
})
})
// 訪問(wèn)不存在的用戶,我們需要構(gòu)造一個(gè)虛假的用戶 id
it('should return 404 when GET /users/${non-existent}', function (done){
request
.get(/users/${ObjectId()}
)
.expect(404, done)
})
// 正常情況下的用戶注冊(cè)不會(huì)帶上 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()
})
})
// 非法情況下的用戶注冊(cè),帶上了 token 的請(qǐng)求要判斷為非法請(qǐng)求
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 編寫(xiě)單元測(cè)試還有一個(gè)優(yōu)點(diǎn),就是可以輕易區(qū)分登錄狀態(tài)和非登錄狀態(tài)。如果要在用戶界面中測(cè)試這些功能,那么就需要不停地登錄和注銷,將會(huì)是一項(xiàng)累人的工作~
另外,上面的例子中基本都是對(duì)返回狀態(tài)嗎進(jìn)行斷言的,你可以按照自己的需要進(jìn)行斷言。
提示
你可以選擇自己喜歡的斷言庫(kù),我這里選擇了 should.js,原因是好讀。
個(gè)人認(rèn)為 should.js 和其他斷言庫(kù)比起來(lái)有個(gè)缺點(diǎn),就是不好寫(xiě)。
value.should.xxx.yyy.zzz 這個(gè)形式和 assert.equal(value, expected) 相比不太直觀。
另外由于 should.js 是通過(guò)擴(kuò)展 Object.prototype 的原型來(lái)實(shí)現(xiàn)的,但 null 值是一個(gè)例外,它不能訪問(wèn)任何屬性。
因此 should.js 在 null 上會(huì)失效。
一個(gè)變通的辦法是 (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)行測(cè)試時(shí),看到自己編寫(xiě)的測(cè)試都通過(guò)時(shí),心里都會(huì)非常踏實(shí)。
而當(dāng)我們要對(duì)項(xiàng)目進(jìn)行重構(gòu)時(shí),這些測(cè)試用例會(huì)幫我們發(fā)現(xiàn)重構(gòu)過(guò)程中的問(wèn)題,減少 Debug 時(shí)間,提升重構(gòu)時(shí)的效率。
在 Node.js 的環(huán)境下,我們可以設(shè)置環(huán)境變量 NODE_ENV=test ,然后通過(guò)這個(gè)環(huán)境變量去連接測(cè)試數(shù)據(jù)庫(kù),這樣測(cè)試數(shù)據(jù)就不會(huì)存在于開(kāi)發(fā)環(huán)境下的數(shù)據(jù)庫(kù)拉!
// config.js
module.exports = {
development: {},
production: {},
test: {}
}
// app.js
const ENV = process.NODE_ENV || 'development'
const config = require('./config')[ENV]
// connect db by config
如何清空測(cè)試數(shù)據(jù)庫(kù)
清空數(shù)據(jù)庫(kù)這種一次性的工作最好放到 npm scripts 中處理,需要進(jìn)行清空操作的時(shí)候直接運(yùn)行 npm run resetDB 就可以了。
需要注意的是,編寫(xiě)清空數(shù)據(jù)庫(kù)腳本時(shí)必須判斷環(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"
},
// ...
}
如果是按照上面的原則來(lái)生成測(cè)試數(shù)據(jù)的話,測(cè)試數(shù)據(jù)其實(shí)可以不用刪掉的。
但由于測(cè)試數(shù)據(jù)會(huì)占用我們的空間,最好還是把這些測(cè)試數(shù)據(jù)刪掉。
那么,清空測(cè)試數(shù)據(jù)庫(kù)這個(gè)操作在測(cè)試前執(zhí)行好,還是測(cè)試后執(zhí)行好?
我個(gè)人傾向于測(cè)試前刪除,因?yàn)橛袝r(shí)候我們需要進(jìn)入數(shù)據(jù)庫(kù),查看測(cè)試數(shù)據(jù)的正確性。
如果在測(cè)試后清空測(cè)試數(shù)據(jù)庫(kù)的話,我們就沒(méi)辦法訪問(wèn)到測(cè)試數(shù)據(jù)了。
{
"scripts": {
"resetDB": "node scripts/resetDB.js",
"test": "NODE_ENV=test npm run resetDB && mocha --harmony"
},
// ...
}
本文章轉(zhuǎn)載微信公眾號(hào)@金陽(yáng)光測(cè)試
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶管理restful api
nodejs + mongodb 編寫(xiě) restful 風(fēng)格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創(chuàng)建REST API
使用 Django 和 Django REST 框架構(gòu)建 RESTful API:實(shí)現(xiàn) CRUD 操作
ASP.NET Web API快速入門介紹
2024年在線市場(chǎng)平臺(tái)的11大最佳支付解決方案
完整指南:如何在應(yīng)用程序中集成和使用ChatGPT API
選擇AI API的指南:ChatGPT、Gemini或Claude,哪一個(gè)最適合你?
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)