鍵.png)
node.js + express + docker + mysql + jwt 實現(xiàn)用戶管理restful api
// 根據(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)
最基本流程是:
· 為 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)時的效率。
在 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"
},
// ...
}
如果是按照上面的原則來生成測試數(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)載微信公眾號@金陽光測試
node.js + express + docker + mysql + jwt 實現(xiàn)用戶管理restful api
nodejs + mongodb 編寫 restful 風(fēng)格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創(chuàng)建REST API
使用 Django 和 Django REST 框架構(gòu)建 RESTful API:實現(xiàn) CRUD 操作
ASP.NET Web API快速入門介紹
2024年在線市場平臺的11大最佳支付解決方案
完整指南:如何在應(yīng)用程序中集成和使用ChatGPT API
選擇AI API的指南:ChatGPT、Gemini或Claude,哪一個最適合你?