mocha誕生於2011年,是一個特徵豐富的javascript測試框架,能夠運行在node.js和瀏覽器上,使異步測試更簡單和有趣。mocha測試連續運行,容許靈活和準確的報告,同時將未捕獲的異常映射到正確的測試用例。javascript
背景
公司項目中,沒有自動化的單元測試,而是經過寫if/else判斷,多少有點懵逼html
所在在種種考慮之下,咱們就選擇mocha測試框架作單元測試前端
測試報告
在terminal裏運行java
npm run mochawesome
完成項目中的單元測試node
單擊 file:///
+項目所在地址+/mochawesome-report/mochawesome.html
react
最終獲得的就是這一份測試報告git
待測試的接口
須要測試的代碼以下github
'use strict';
const router = require('express').Router();const passport = require('passport');const User = require('../collections/user');const log = require('../services/logger').createLogger('userAuthentication');const AUTH_ERR = require('../constant/errMessage').AUTH;const COMM_ERR = require('../constant/errMessage').COMMON;
/** * @api {get} /v1/auth/ User auth information * @apiName UserAuthInfo * @apiGroup userAuthentication * * @apiParam {null} null. * * @apiSuccess {String} username The username of the current user. * @apiSuccess {date} last User last logon time. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "test", * "last": "2019-06-03T06:22:53.567Z" * } * * @apiError NOT_LOGIN The current User was not logon. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "NOT_LOGIN", * "message": "User has not logon in!" * } */
router.get('/', function(req, res) { if (req.user) { res.json({ username: req.user.username, last: req.user.last }); } else { res.status(401).json({ err: 'NOT_LOGIN', message: AUTH_ERR.NOT_LOGIN }); }});
/** * @api {post} /v1/auth/register User Register * @apiName UserRegister * @apiGroup userAuthentication * * @apiParam {String} username New user's name. * @apiParam {String} password New user's password. * * @apiSuccess {String} username The username of the register user. * @apiSuccess {string} message The registering success info. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "gushen", * "message": "User registered successful" * } * * @apiError REGISTER_FAILURE The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 500 Internal Server Error * { * "err": "REGISTER_FAILURE", * "message": "User register failure!" * } */router.post('/register', function(req, res, next) { User.register(new User({ username: req.body.username }), req.body.password, function(err) { if (err) { log.error(err); res.status(500).json({ err: 'REGISTER_FAILURE', message: AUTH_ERR.REGISTER_FAILURE }); return; }
log.info('user ' + req.body.username + ' registered successful!'); res.json({ username: req.body.username, message: 'User registered successful' });
});});
/** * @api {post} /v1/auth/login User login * @apiName UserLogin * @apiGroup userAuthentication * * @apiParam {String} username User's name. * @apiParam {String} password User's password. * * @apiSuccess {String} username The username of the register user. * @apiSuccess {string} message The messgaer if the user login in successful. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "test", * "message": "Authentication Success" * } * * @apiError REGISTER_FAILURE The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "AUTHENTICATE_FAILURE", * "message": "Authenticate failure" * } */router.post('/login', isAhenticated, passport.authenticate('local'), function(req, res) { if (req.user) { log.info(`${req.user.username} login in successful`); res.json({ username: req.user.username, message: 'Authentication Success' }); return; } log.info(`${req.user.username} login failure`); res.status(401).json({ err: 'AUTHENTICATE_FAILURE', message: `${req.user.username} login failure` });
});
/** * @api {post} /v1/auth/user/:username User delete * @apiName UserDelete * @apiGroup userAuthentication * * @apiParam {String} username User's name. * * @apiSuccess {String} username The username of the deleted user. * @apiSuccess {string} message The message if deleting successful. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "gushen", * "message": "Delete User Successful" * } * * @apiError NOT_LOGIN The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "NOT_LOGIN", * "message": "User has not logon in!" * } */router.delete('/user/:username', function(req, res) { if (!req.user) { res.status(401).json({ err: 'NOT_LOGIN', message: AUTH_ERR.NOT_LOGIN }); return; }
// if (!req.params.username) { // res.json({ // err: 'PARAMS_NOT_CORRECT', // message: 'No deleted user name' // }); // return; // }
User.deleteOne({ username: req.params.username }, (err) => { if (err) { log.error(err); res.status(500).json({ err: 'SERVER_ERROR', message: COMM_ERR.SERVER_ERROR }); return; }
res.json({ username: req.params.username, message: 'Delete User Successful' });
log.info(`${req.params.username} has been deleted`); });});
/** * @api {post} /v1/auth/changepassword User change password * @apiName UserChangePassword * @apiGroup userAuthentication * * @apiParam {String} username User's name. * @apiParam {String} oldpassword User's old password. * @apiParam {String} newpassword User's old password. * * @apiSuccess {String} username The username of the user. * @apiSuccess {string} message The message if changing password successful. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "test", * "message": "change password successful" * } * * @apiError AUTHENTICATE_FAILURE The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "AUTHENTICATE_FAILURE", * "message": "Password or username is incorrect" * } */router.post('/changepassword', function(req, res) { User.findOne({ 'username': req.body.username }, (err, user) => { if (err) { log.error(err); res.status(500).json({ err: 'SERVER_ERROR', message: COMM_ERR.SERVER_ERROR }); return; }
if (!user) { res.status(500).json({ err: 'USER_NOT_EXIST', message: AUTH_ERR.USER_NOT_EXIST }); return; }
user.changePassword(req.body.oldpassword, req.body.newpassword, (err, value) => { if (err) { log.error(err); res.status(401).json({ err: 'AUTHENTICATE_FAILURE', message: err.message }); return; }
log.info(`${req.body.username} change password successful`); res.json({ username: req.body.username, message: 'change password successful' });
}); });});
/** * @api {get} /v1/auth/logout User login out * @apiName UserLogout * @apiGroup userAuthentication * * @apiSuccess {String} username The username of the user. * @apiSuccess {string} message The message if user login out successful. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "test", * "message": "logout successful" * } * * @apiError NOT_LOGIN There is no user logon in. * * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * { * "err": "NOT_LOGIN", * "message": "No user has been logon" * } */router.get('/logout', function(req, res) { const user = req.user; if (!user) { res.status(401).json({ err: 'NOT_LOGIN', message: 'No user has been logon' }); return; }
// user login out req.logout(); if (!req.user) { res.json({ username: user.username, message: 'logout successful' });
log.info(`${user.username} has been logon out`); return; }
res.status(500).json({ err: 'SERVER_ERROR', message: 'logout failure!' });
});
function isAhenticated(req, res, next) { User.findOne({ 'username': req.body.username }, (err, user) => { if (err) { log.error(err); res.json({ err: 'SERVER_ERROR', message: COMM_ERR.SERVER_ERROR }); return; } // If user is not existed if (!user) { res.json({ err: 'USER_NOT_EXIST', message: AUTH_ERR.USER_NOT_EXIST });
return; }
user.authenticate(req.body.password, (err, value) => { if (err) { log.error(err); res.json({ err: 'SERVER_ERROR', message: COMM_ERR.SERVER_ERROR }); } else if (value) { return next(); } else { res.json({ err: 'AUTHENTICATE_FAILURE', message: AUTH_ERR.AUTHENTICATE_FAILURE }); } }); });}
module.exports = router;
這是一套常見的有關用戶登陸註冊驗證的接口web
由於文本只涉及到這個模塊,因此將這個模塊的接口都寫在userAuthentication
測試套件下express
describe('userAuthentication', function() {}
測試代碼
'use strict';
const request = require('supertest');const url = 'http://localhost:5001';// eslint-disable-next-line no-unused-varsconst should = require('should');
var userCookie;
// 用戶名密碼const user = { username: 'name', password: 'password'};
// 測試更改密碼(每次測試完調換)const user2 = { username: 'uu2', password: 'oldpassword'};
const newUser2 = { username: 'uu2', oldpassword: 'oldpassword', newpassword: 'newpassword'};
// const user22={// username: 'uu2',// password: 'newpassword'// };// const oldUser2 = {// username: 'uu2',// oldpassword: 'newpassword',// newpassword: 'oldpassword'// };
describe('userAuthentication', function() { // 測試註冊接口 describe('UserRegister', function() { describe('POST /register', function() { // eslint-disable-next-line max-len it('register success', function(done) { request(url) .post('/api/v1/auth/register') .send(user) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'User registered successful' }); if (err) throw err; done(); }); }); it('repeated registration failure.', function(done) { request(url) .post('/api/v1/auth/register') .send(user) .expect(500) .end(function(err, res) { res.body.should.containEql({ err: 'REGISTER_FAILURE' }); if (err) throw err; done(); }); }); }); }); // 測試登陸接口 describe('UserLogin', function() { describe('POST /login', function() { it('login success', function(done) { request(url) .post('/api/v1/auth/login') .send(user) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'Authentication Success' }); if (err) throw err; done(); }); }); it('USER_NOT_EXIST.', function(done) { request(url) .post('/api/v1/auth/login') .send({ username: 'a', password: 'admin' }) .expect(200) .end(function(err, res) { res.body.should.containEql({ err: 'USER_NOT_EXIST' }); if (err) throw err; done(); }); }); }); }); // 權限驗證 describe('UserAuthInfo', function() { describe('GET /api/v1/auth/', function() { // 沒有登陸,權限驗證 it('The current User was not login.', function(done) { request(url) .get('/api/v1/auth/') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 權限驗證前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('The username of the current user.', function(done) { request(url) .get('/api/v1/auth/') .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.have.keys('username'); if (err) throw err; done(); }); }); }); }); // 測試用戶註銷接口 describe('UserLogout', function() { describe('GET /logout', function() { // 沒有登陸,測試註銷 it('NOT_LOGIN.', function(done) { request(url) .get('/api/v1/auth/logout') .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 註銷成功前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('logout successful.', function(done) { request(url) .get('/api/v1/auth/logout') .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'logout successful' }); if (err) throw err; done(); }); }); }); }); // 測試更改用戶密碼接口 describe('UserChangePassword', function() { describe('POST /changepassword', function() { // 更改用戶密碼前先註冊-登陸 // eslint-disable-next-line no-undef before(function(done) { request(url) .post('/api/v1/auth/register') .send(user2) .end(function(err, res) { if (err) throw err; done(); }); }); // eslint-disable-next-line no-undef before(function(done) { request(url) .post('/api/v1/auth/login') .send(user2) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('change password successful', function(done) { request(url) .post('/api/v1/auth/changepassword') .set('Cookie', userCookie) .send(newUser2) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'change password successful' }); if (err) throw err; done(); }); }); it('AUTHENTICATE_FAILURE', function(done) { request(url) .post('/api/v1/auth/changepassword') .set('Cookie', userCookie) .send(newUser2) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'AUTHENTICATE_FAILURE' }); if (err) throw err; done(); }); }); // after(function(done) { // request(url) // .post('/api/v1/auth/login') // .send(user22) // .set('Accept', 'application/json') // .end(function(err, res) { // if (!err) { // userCookie = res.header['set-cookie']; // done(); // } // }); // }); // after(function(done) { // request(url) // .post('/api/v1/auth/changepassword') // .set('Cookie', userCookie) // .send(oldUser2) // .expect(200) // .end(function(err, res) { // res.body.should.containEql({ // message: 'rechange password successful' // }); // if (err) throw err; // done(); // }); // }); }); }); // 測試刪除用戶接口 describe('UserDelete', function() { describe('DELETE /user/:username', function() { it('NOT_LOGIN.', function(done) { request(url) .delete(`/api/v1/auth/user/${user.username}`) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 刪除用戶前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('delete user success', function(done) { request(url) .delete(`/api/v1/auth/user/${user.username}`) .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'Delete User Successful' }); if (err) throw err; done(); }); }); }); });});
測試前的準備
測試框架
所謂"測試框架",就是運行測試的工具。經過它,能夠爲JavaScript應用添加測試,從而保證代碼的質量。
一般應用會有單元測試(Unit tests)和功能測試(Functional tests),複雜大型應用可能會有整合測試(Integration tests)。
1.單元測試:關注應用中每一個零部件的正常運轉,防止後續修改影響以前的組件。2.功能測試:確保其總體表現符合預期,關注可否讓用戶正常使用。3.整合測試:確保單獨運行正常的零部件整合到一塊兒以後依然能正常運行。
開發人員主要是集中單元測試,做爲開發中的反饋。
單元測試的好處:
1.若是能經過單元測試,那麼經過後續測試且軟件總體正常運行的機率大大提升。2.單元測試發現的問題定位到細節,容易修改,節省時間。3.追蹤問題變得更加方便。
選擇單元測試框架
單元測試應該:簡單,快速執行,清晰的錯誤報告。測試框架基本上都作了同一件事兒:
1.描述你要測試的東西2.對其進行測試3.判斷是否符合預期
選擇框架會考慮下面的點:
1.斷言(Assertions):用於判斷結果是否符合預期。有些框架須要單獨的斷言庫。2.適合 TDD / BDD:是否適合 測試驅動型 / 行爲驅動型 的測試風格。3.異步測試:有些框架對異步測試支持良好。4.使用的語言:大部分 js 測試框架使用 js。5.用於特定目的:每一個框架可能會擅長處理不一樣的問題。6.社區是否活躍。注:7.TDD:測試驅動型的開發方式,先寫測試代碼,以後編寫能經過測試的業務代碼,能夠不斷的在能經過測試的狀況下重構。8.BDD:與 TDD 很類似,測試代碼的風格是預期結果,更關注功能,看起來像需求文檔。
其實都是先寫測試代碼,感受BDD 風格更人性。
測試工具的類型
組合使用工具很常見,即便已選框架也能實現相似的功能
1.提供測試框架(Mocha, Jasmine, Jest, Cucumber)2.提供斷言(Chai, Jasmine, Jest, Unexpected)3.生成,展現測試結果(Mocha, Jasmine, Jest, Karma)4.快照測試(Jest, Ava)5.提供仿真(Sinon, Jasmine, enzyme, Jest, testdouble)6.生成測試覆蓋率報告(Istanbul, Jest, Blanket)7.提供類瀏覽器環境(Protractor, Nightwatch, Phantom, Casper)
解釋上面提到的點:
1.測試框架,即組織你的測試,當前流行 BDD 的測試結構。2.快照測試(snapshot testing),測試 UI 或數據結構是否和以前徹底一致,一般 UI 測試不在單元測試中3.仿真(mocks, spies, and stubs):獲取方法的調用信息,模擬方法,模塊,甚至服務器
Jest/Jasmine/Mocha框架特色
•Jest
•facebook 坐莊•基於 Jasmine 至今已經作了大量修改添加了不少特性•開箱即用配置少,API簡單•支持斷言和仿真•支持快照測試•在隔離環境下測試•互動模式選擇要測試的模塊•優雅的測試覆蓋率報告,基於Istanbul•智能並行測試(參考)•較新,社區不十分紅熟•全局環境,好比 describe 不須要引入直接用•較多用於 React 項目(但普遍支持各類項目)
•Jasmine •開箱即用(支持斷言和仿真)•全局環境•比較'老',坑基本都有人踩過了•AVA•異步,性能好•簡約,清晰•快照測試和斷言須要三方支持•Tape•體積最小,只提供最關鍵的東西•對比其餘框架,只提供最底層的 API
•Mocha
•靈活(不包括斷言和仿真,本身選對應工具)•流行的選擇:chai,sinon•社區成熟用的人多,測試各類東西社區都有示例•須要較多配置•可使用快照測試,但依然須要額外配置
綜上所述,Mocha 用的人最多,社區最成熟,靈活,可配置性強易拓展,Jest 開箱即用,裏邊啥都有提供全面的方案,Tape 最精簡,提供最基礎的東西最底層的API。因此本文就選擇用mocha。
mocha特徵
1.瀏覽器支持2.全局變量泄漏檢測3.簡單異步支持,包括promise4.能夠選擇運行與regexp匹配的測試5.測試覆蓋率報告6.自動退出以防止活動循環「掛起」7.字符串差別支持8.易於元生成套件和測試用例9.用於運行測試的javascript API10.配置文件支持11.CI支持等的正確退出狀態12.mocha.opts文件支持13.自動檢測和禁用非tty的着色14.可單擊套件標題以篩選測試執行15.將未捕獲的異常映射到正確的測試用例16.節點調試器支持17.異步測試超時支持18.檢測多個要完成的調用19.測試重試支持20.測試特定超時21.咆哮支持22.報告測試持續時間23.突出顯示慢速測試24.文件監視程序支持25.使用所需的任何斷言庫26.extensible reporting, bundled with 9+ reporters27.可擴展測試DSL或「接口」28.每一個鉤子以前、以後、全部鉤子以前、以後29.任意蒸騰器支持(coffee-script 等)30.TextMate bundle
斷言庫should
Mocha自己是不包含斷言庫的,因此咱們須要本身選擇斷言庫。should是一個很簡單的、貼近天然語言的斷言庫。固然,Mocha是適配全部的斷言庫的,若是你喜歡其餘的斷言庫好比expect之類的,你也能夠把它包含進來使用。
http測試模塊SuperTest
單單使用Mocha和should就幾乎能夠知足全部JavaScript函數的單元測試。可是對於Node應用而言,不只僅是函數的集合,好比一個web應用的測試。這時候就須要配合一個http代理,完成Http請求和路由的測試。Supertest是一個HTTP代理服務引擎,能夠模擬一切HTTP請求行爲。Supertest能夠搭配任意的應用框架,從而進行應用的單元測試。
測試套件describe
describe塊稱爲"測試套件"(test suite),表示一組相關的測試。它是一個函數,第一個參數是測試套件的名稱("加法函數的測試"),第二個參數是一個實際執行的函數。
測試用例it
it塊稱爲"測試用例"(test case),表示一個單獨的測試,是測試的最小單位。它也是一個函數,第一個參數是測試用例的名稱("1 加 1 應該等於 2"),第二個參數是一個實際執行的函數。
鉤子hooks
Mocha在describe塊之中,提供測試用例的四個鉤子:before()、after()、beforeEach()和afterEach()。它們會在指定時間執行。
describe('hooks', function() {
before(function() { // 在本區塊的全部測試用例以前執行 });
after(function() { // 在本區塊的全部測試用例以後執行 });
beforeEach(function() { // 在本區塊的每一個測試用例以前執行 });
afterEach(function() { // 在本區塊的每一個測試用例以後執行 });
// test cases});
安裝mocha> = v3.0.0,npm的版本應該> = v2.14.2。除此,確保使用Node.js的版本> = v4來運行mocha
mocha小例子
安裝
做爲項目的依賴進行安裝
npm install --save-dev mocha
開始
mkdir testcd testtouch test.js
加入測試代碼
'use strict'var assert = require('assert');describe('Array', function() { describe('#indexOf()', function() { it('should return 0 when the value is not present', function() { assert.equal([1, 2, 3].indexOf(1), 1); }); });});
安裝依賴
npm install --save-dev assert
執行測試
./node_modules/mocha/bin/mocha
報錯結果
Array #indexOf() 1) should return 0 when the value is not present
0 passing (5ms) 1 failing
1) Array #indexOf() should return 0 when the value is not present:
AssertionError [ERR_ASSERTION]: 0 == 1 + expected - actual
-0 +1
at Context.<anonymous> (test/test.js:6:14)
package.json中寫入命令
"mocha": "mocha"
執行命令
npm run mocha
正確測試
'use strict'var assert = require('assert');describe('Array', function() { describe('#indexOf()', function() { it('should return 0 when the value is not present', function() { assert.equal([1, 2, 3].indexOf(1), 0); }); });});
正確結果
Array #indexOf() ✓ should return 0 when the value is not present
1 passing (4ms)
到這裏,對mocha就有了初步的認識
開始測試
瞭解了背景和框架後,正式開啓測試
添加依賴
npm install --save-dev mocha mochawesome should supertest
在scripts中添加命令
"mochawesome": "./node_modules/.bin/mocha --reporter mochawesome","dev": "node index.js"
mochawesome生成報告
dev啓動項目
註冊接口的測試
1.從註冊接口中,我得知該接口返回兩個狀態碼,分別是200和500,對應的註冊成功和註冊失敗2.那麼測試中就有兩個註冊成功和失敗的測試用例3.每一個測試用例針對每一個狀態返回的值判斷4.經過便可5.不經過,要麼是接口有問題,要麼是寫的測試有問題
/** * @api {post} /v1/auth/register User Register * @apiName UserRegister * @apiGroup userAuthentication * * @apiParam {String} username New user's name. * @apiParam {String} password New user's password. * * @apiSuccess {String} username The username of the register user. * @apiSuccess {string} message The registering success info. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "username": "gushen", * "message": "User registered successful" * } * * @apiError REGISTER_FAILURE The register failure. * * @apiErrorExample Error-Response: * HTTP/1.1 500 Internal Server Error * { * "err": "REGISTER_FAILURE", * "message": "User register failure!" * } */router.post('/register', function(req, res, next) { User.register(new User({ username: req.body.username }), req.body.password, function(err) { if (err) { log.error(err); res.status(500).json({ err: 'REGISTER_FAILURE', message: AUTH_ERR.REGISTER_FAILURE }); return; }
log.info('user ' + req.body.username + ' registered successful!'); res.json({ username: req.body.username, message: 'User registered successful' });
});});
1.UserRegister
是第二層套件2.POST /register
是第三層套件3.register success
是測試用例名稱4.it的方法是測試用例的方法5.請求url地址6.發送post請求7.發送用戶名和密碼8.斷言狀態碼9.斷言返回的值10.執行下一步
注意: 每一個測試用例結束後必須帶上done
,不然沒有結束標識,會超時報錯
// 測試註冊接口 describe('UserRegister', function() { describe('POST /register', function() { // eslint-disable-next-line max-len it('register success', function(done) { request(url) .post('/api/v1/auth/register') .send(user) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'User registered successful' }); if (err) throw err; done(); }); }); it('repeated registration failure.', function(done) { request(url) .post('/api/v1/auth/register') .send(user) .expect(500) .end(function(err, res) { res.body.should.containEql({ err: 'REGISTER_FAILURE' }); if (err) throw err; done(); }); }); }); });
登陸接口的測試
沒什麼好講的,同測試註冊接口步驟一致
describe('UserLogin', function() { describe('POST /login', function() { it('login success', function(done) { request(url) .post('/api/v1/auth/login') .send(user) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'Authentication Success' }); if (err) throw err; done(); }); }); it('USER_NOT_EXIST.', function(done) { request(url) .post('/api/v1/auth/login') .send({ username: 'a', password: 'admin' }) .expect(200) .end(function(err, res) { res.body.should.containEql({ err: 'USER_NOT_EXIST' }); if (err) throw err; done(); }); }); }); });
權限驗證的測試
1.權限驗證就有點不同了,由於驗證權限前必須先登陸,這時候就要用上mocha的鉤子2.權限是經過cookie驗證,因此驗證前必需要帶上cookie3.在before
鉤子中加入
userCookie = res.header['set-cookie'];
4.在斷言的請求中帶上Cookie
.set('Cookie', userCookie)
// 權限驗證describe('UserAuthInfo', function() { describe('GET /api/v1/auth/', function() { // 沒有登陸,權限驗證 it('The current User was not login.', function(done) { request(url) .get('/api/v1/auth/') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 權限驗證前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('The username of the current user.', function(done) { request(url) .get('/api/v1/auth/') .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.have.keys('username'); if (err) throw err; done(); }); }); });});
用戶註銷接口的測試
沒什麼好講的,同測試權限驗證步驟一致
describe('UserLogout', function() { describe('GET /logout', function() { // 沒有登陸,測試註銷 it('NOT_LOGIN.', function(done) { request(url) .get('/api/v1/auth/logout') .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 註銷成功前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('logout successful.', function(done) { request(url) .get('/api/v1/auth/logout') .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'logout successful' }); if (err) throw err; done(); }); }); }); });
更改用戶密碼的測試
更改用戶密碼前先註冊-登陸
// 測試更改用戶密碼接口 describe('UserChangePassword', function() { describe('POST /changepassword', function() { // 更改用戶密碼前先註冊-登陸 // eslint-disable-next-line no-undef before(function(done) { request(url) .post('/api/v1/auth/register') .send(user2) .end(function(err, res) { if (err) throw err; done(); }); }); // eslint-disable-next-line no-undef before(function(done) { request(url) .post('/api/v1/auth/login') .send(user2) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('change password successful', function(done) { request(url) .post('/api/v1/auth/changepassword') .set('Cookie', userCookie) .send(newUser2) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'change password successful' }); if (err) throw err; done(); }); }); it('AUTHENTICATE_FAILURE', function(done) { request(url) .post('/api/v1/auth/changepassword') .set('Cookie', userCookie) .send(newUser2) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'AUTHENTICATE_FAILURE' }); if (err) throw err; done(); }); }); // after(function(done) { // request(url) // .post('/api/v1/auth/login') // .send(user22) // .set('Accept', 'application/json') // .end(function(err, res) { // if (!err) { // userCookie = res.header['set-cookie']; // done(); // } // }); // }); // after(function(done) { // request(url) // .post('/api/v1/auth/changepassword') // .set('Cookie', userCookie) // .send(oldUser2) // .expect(200) // .end(function(err, res) { // res.body.should.containEql({ // message: 'rechange password successful' // }); // if (err) throw err; // done(); // }); // }); }); });
問題是我改完後得將密碼改回來,這一步我沒有成功,很奇怪爲何?
目前得每次測試完後將新舊密碼調換,真的很麻煩
刪除用戶的測試
沒什麼好講的,同測試權限驗證步驟一致
describe('UserDelete', function() { describe('DELETE /user/:username', function() { it('NOT_LOGIN.', function(done) { request(url) .delete(`/api/v1/auth/user/${user.username}`) .expect(401) .end(function(err, res) { res.body.should.containEql({ err: 'NOT_LOGIN' }); if (err) throw err; done(); }); }); // 刪除用戶前先登陸 beforeEach(function(done) { request(url) .post('/api/v1/auth/login') .send(user) .set('Accept', 'application/json') .end(function(err, res) { if (!err) { userCookie = res.header['set-cookie']; done(); } }); }); it('delete user success', function(done) { request(url) .delete(`/api/v1/auth/user/${user.username}`) .set('Cookie', userCookie) .expect(200) .end(function(err, res) { res.body.should.containEql({ message: 'Delete User Successful' }); if (err) throw err; done(); }); }); }); });
未完待續
口渴了,快去喝杯mocha吧
參考文獻
mocha官網[1]
測試框架 Mocha 實例教程[2]
前端測試框架對比(js單元測試框架對比)[3]
mocha 和 supertest[4]
使用mocha給mongoose寫單元測試[5]
nodejs使用mocha進行接口測試[6]
node中使用 mocha + supertest + should 來寫單元測試[7]
【Node開發筆記】單元測試工具Mocha和SuperTest[8]
一步一步搭建react應用-node中使用 mocha + supertest + should 來寫單元測試[9]
mocha + chai + supertest 測試 node server[10]
Should.js[11]
接口自動化 開源框架學習-supertest[12]
supertest[13]
Nodejs單元測試小結[14]
使用mocha進行單元測試[15]
References
[1]
mocha官網: https://mochajs.org/[2]
測試框架 Mocha 實例教程: http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html[3]
前端測試框架對比(js單元測試框架對比): https://www.cnblogs.com/lihuanqing/p/8533552.html[4]
mocha 和 supertest: https://github.com/nswbmw/N-blog/blob/master/book/4.14%20%E6%B5%8B%E8%AF%95.md#4141-mocha-%E5%92%8C-supertest[5]
使用mocha給mongoose寫單元測試: http://ju.outofmemory.cn/entry/86908[6]
nodejs使用mocha進行接口測試: https://blog.csdn.net/weixin_34308389/article/details/87441278[7]
node中使用 mocha + supertest + should 來寫單元測試: https://github.com/xiyuyizhi/movies/blob/master/dayByday/day5.md[8]
【Node開發筆記】單元測試工具Mocha和SuperTest: https://www.imooc.com/article/2631[9]
一步一步搭建react應用-node中使用 mocha + supertest + should 來寫單元測試: https://segmentfault.com/a/1190000011095213[10]
mocha + chai + supertest 測試 node server: https://webfem.com/post/mocha-test[11]
Should.js: http://shouldjs.github.io/[12]
接口自動化 開源框架學習-supertest: https://blog.csdn.net/lichao330530/article/details/51907075[13]
supertest: https://www.npmjs.com/package/supertest[14]
Nodejs單元測試小結: https://segmentfault.com/a/1190000002921481?utm_source=tag-newest[15]
使用mocha進行單元測試: https://www.jianshu.com/p/47575895bc54
本文分享自微信公衆號 - 全棧大佬的修煉之路(gh_7795af32a259)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。