在平常的功能開發中,咱們的代碼測試都依賴於本身或者QA進行測試。這些操做不只費時費力,並且還依賴開發者自身的驅動。在開發一些第三方依賴的庫時,咱們也沒有辦法給第三方提供完整的代碼質量報告。javascript
如今,咱們可使用單元測試來提升本身的代碼質量。下面,我將本身在使用Jest和Sinon.js配置和編寫單元測試中的收穫的經驗和踩到的坑進行總結,根據從零開始配置和編寫單元測試這一條線來進行分享。html
經過本文,你能夠解決如下問題:java
Jest是FaceBook推出的一個針對JavaScript進行單元測試的庫,它提供了斷言、函數模擬等API來對你本身編寫的業務邏輯代碼進行測試後。webpack
Sinon.js是一個用來作獨立測試和模擬的JavaScript庫。它在單元測試的編寫中一般用來模擬HTTP等相關請求。git
在最開始的框架選擇中,我先嚐試了可以並行測試,大大提升單元測試速度的ava框架。它能知足平常的普通需求如utils工具集的測試,也可以配置Sinon.js來進行HTTP模擬測試。github
可是,在處理webpack alias的問題時,經過官方issue中的極其複雜的配置也沒有可以解決出現Cannot find module
的問題(其中一個解決此問題的插件babel-plugin-webpack-loaders中居然是推薦直接使用Jest,囧)。web
而在Jest中,能夠很方便的經過一些簡單配置,就可以識別在文件中使用的webpack alias,相關的具體方法將會在後面章節進行具體描述。npm
而對於其餘的測試框架如:Mocha或者Chai等,沒有進行具體的瞭解,所以在這裏很少作評價。json
須要使用Jest,首先你須要進行安裝,執行如下命令:bash
npm install jest -D
複製代碼
若是你的項目中存在.babelrc
文件(使用了babel 6)時,不論你測試的代碼是否經過babel進行編譯,你都須要安裝額外的幾個包:
npm install babel-jest babel-core regenerator-runtime -D
複製代碼
若是你使用的是babel 7,則須要安裝下面幾個包:
npm install babel-jest 'babel-core@^7.0.0-0' @babel/core regenerator-runtime -D
複製代碼
在安裝完成依賴包之後,若是你有相關的jest配置項須要設置,你還能夠在package.json
文件中配置以下字段:
{
"jest": {
}
}
複製代碼
.babelrc
文件只須要保存以前的配置,不須要作任何修改便可生效。
安裝配置完了Jest,讓咱們來看下Sinon.js。須要使用Sinon.js,咱們首先須要進行安裝:
npm install sinon -D
複製代碼
配置完成後,須要在使用的地方進行引入,以下所示:
const sinon = require('sinon');
複製代碼
在個人項目中,主要是使用Sinon.js來模擬HTTP請求。在Sinon.js的文檔中,有專門關於XMLHttpRequest對象的模擬的章節,在下一章中,咱們將會針對項目中sinon.js的使用進行簡單的介紹。
在本章中,咱們會針對如何編寫單元測試文件進行一個具體的講解,其中包含:
同時,咱們會對當中使用到的Jest和Sinon.js的API會進行簡單介紹,若是須要使用其餘的API,能夠自行閱讀Jest和Sinon.js的文檔。
經過上面三類測試,咱們基本可以覆蓋現有項目中的全部代碼。
同步函數的測試過程是這幾個中最簡單的一部分,咱們能夠測試函數返回值,也可以測試傳入的高階函數。下面咱們經過一個具體的例子來看下。
源代碼文件,一個純函數:
// user.js
export default function(obj) {
return 'hjava';
}
export function handleUserData(callback) {
callback('hjava');
}
複製代碼
針對上面的源代碼文件編寫的一個單元測試文件:
// user.test.js
import userFunc, {handleUserData} from './user';
// test是一個註冊的全局方法
test('user', () => {
expect(userFunc()).toBe('hjava'); // 判斷userFunc的執行結果等於'hjava'
let callback = jest.fn(); // jest是一個註冊的全局變量
handleUserData(callback);
expect(callback.mock.calls.length).toBe(1); // 判斷callback函數被調用了一次
expect(callback.mock.calls[0][0]).toBe('hjava'); // 判斷了callback函數的第一次被調用的第一個參數爲'hjava'
});
複製代碼
從上面的示例中咱們能夠看到,針對同步的純函數,咱們能夠經過很簡單的單元測試模型來驗證它的功能。
異步函數主要分爲兩種——Callback方式和Promise方式。這兩種方式都很簡單,下面咱們對兩種方式進行具體的介紹。詳細內容能夠見Jest文檔中的測試異步代碼。
// user.js
export default function(callback) {
setTimeout(()=>{
callback({username: 'hjava'});
}, 1000);
}
複製代碼
// user.test.js
import userFunc from './user';
test('user', () => {
userFunc((data) => {
expect(data).toEqual({username: 'hjava'}); // 對象比較用beEqual()
});
});
複製代碼
// user.js
export default function(callback) {
return Promise.resolve({username: 'hjava'});
}
複製代碼
// user.test.js
import userFunc from './user';
test('user', () => {
userFunc().then((data) => {
expect(data).toEqual({username: 'hjava'});
});
});
複製代碼
在測試HTTP請求相關參數的過程當中,咱們須要模擬XMLHttpRequest對象,從而攔截相關的HTTP請求,獲取請求數據。正好Sinon.js可以作到這一點。下面咱們經過一個示例來看下相關的邏輯:
// user.js
export default function(callback) {
this.sendRequest('/user/get', callback); // 發送請求來獲取用戶數據,成功後執行callback回調函數
}
複製代碼
// user.test.js
import Sinon from 'sinon';
import userFunc from 'user';
let XHR;
let requests = [];
// beforeEach是Jest提供的函數,在每一個測試執行前都會執行一次
beforeEach(() => {
XHR = sinon.useFakeXMLHttpRequest(); //建立一個模擬的XMLHttpRequest對象
XHR.onCreate = function (xhr) {
requests.push(xhr);
};
});
// afterEach是Jest提供的函數,在每一個測試執行後都會執行一次
afterEach(() => {
XHR.restore();
});
test('user', () => {
let callback = jest.fn();
HTTPCommon.deleteRemoteSession({
data: {},
success: callback
});
expect(requests.length).toBe(1);
requests[0].respond(200, {"Content-Type": 'application/json'}, 'hjava'); // 模擬返回值
expect(callback.mock.calls[0][0]).toBe('hjava');
});
複製代碼
在本章中,咱們總結了以下問題來進行介紹,但願你們再遇到相同問題時可以快速解決:
不像ava同樣,須要使用syc來進行計算,Jest內置了統計單元測試覆蓋率的工具,只須要簡單配置便可達到相關的要求。具體配置以下:
// package.json
{
"jest": {
"collectCoverage": true, // 是否開啓統計單元測試覆蓋率
"collectCoverageFrom": [ // 指定統計單元測試覆蓋率文件
"**/src/**.js"
],
}
}
複製代碼
若是你的項目中有.babelrc
文件,而你不但願單元測試文件受到babel文件的影響,你能夠在jest的配置項中增長transform
字段,具體配置以下:
// package.json
{
"jest": {
"transform": {}
}
}
複製代碼
若是你的單元測試文件中須要使用ES2015後經過babel來進行編譯,那麼須要對.babelrc
文件的配置進行部分修改。
若是你以前在.babelrc
文件中,把modules
字段設置爲false,那麼你須要在test
環境下從新開啓,具體代碼以下:
// .babelrc
{
"presets": [["env", {"modules": false}]],
"env": {
"test": {
"presets": [["env"]]
}
}
}
複製代碼
若是你使用的是babel 7的話(安裝時多安裝過相關依賴包),你須要設置的presets
字段的值應該爲@babel/env
,具體代碼以下:
// .babelrc
{
"presets": [["env", {"modules": false}]],
"env": {
"test": {
"presets": [["@babel/env"]]
}
}
}
複製代碼
若是咱們在項目中使用了webpack,那麼咱們很大機率會使用到alias相關屬性來定義路徑。可是,在單元測試框架中,它並不可以識別這種路徑,就會出現Cannot find module 'xxx' from 'yyy'
的報錯。
不像ava框架須要安裝插件和進行復雜的配置,咱們只須要在Jest中配置moduleNameMapper
屬性便可知足需求。具體示例以下:
// webpack.config.js
{
alias: {
'@__dir':process.cwd()
}
}
複製代碼
//package.json
{
"jest": {
"moduleNameMapper": {
"@__dir(.*)$": "<rootDir>$1" //正則匹配方式,對應webpack alias
}
}
}
複製代碼
編寫測試是一個很好的習慣。
不少人常常都說要對本身的代碼進行質量監控,可是又不知道該如何下手。經過這篇文章,你應該學會了如何針對已有代碼從零開始編寫一套完整的單元測試用例。
若是有任何疑問,歡迎留言或者私信進行溝通與交流。
關於Jest是如何測試JavaScript代碼以及Sinon是如何模擬XMLHttpRequest請求的,咱們將會在後面幾篇博客中給你們帶來相關的源碼解析,有興趣的同窗能夠關注我,留意後續的文章。