Jest、Enzyme 簡介
Jest 是 Facebook 發佈的一個開源的、基於 Jasmine 框架的 JavaScript 單元測試工具。
Enzyme 是 React 的測試類庫。 Enzyme 提供了一套簡潔強大的 API,並通過 jQuery 風格的方式進行DOM 處理,開發體驗十分友好。
普通方法測試
首先,使用npm
安裝Jest
npm install --save-dev jest
在目錄下新建一個待測試文件 sort.js
。
function sort(sortArr) {
return sortArr.sort((a, b) => a - b);
}
module.exports = sort;
此處sort方法未對入參做類型檢測
在這裏定義了一個數組排序方法,下面來書寫其測試用例,在目錄下新建一個sort.test.js
文件。
const sort = require('./sort');
const arr = [5,2,4,3,1];
test('排序數組[5,2,4,3,1]', () => {
expect(sort(arr)).toEqual([1,2,3,4,5]);
})
在用例中,我們先引入了待測試的方法,接下來定義了一個排序數組[5,2,4,3,1]的測試用例.test()
用來定義一個測試用例,expect()
會執行內部的方法,返回一個待測試的結果。toEqual()
用來判斷返回的結果於期望的結果是否相等。這裏由於期望返回結果爲數組,所以使用toEqual
進行判斷,除此之外,還有toBe()
,toBeNull()
等方法來比較不同的類型。更多內容...
打開package.json
,在scripts
中新增
test: "jest"
然後運行命令
npm run test
會看到用例測試通過的信息
由於我們的方法沒有做入參類型檢測,下面通過傳入字符串,來測試異常情況。在sort.test.js
中新增一個測試用例用例
test('排序字符串“52431”', () => {
expect(sort('52431')).toEqual(12345);
})
運行,則會看到測試失敗的信息
從測試結果中我們可以清除的看到,運行來兩個測試用例,第一個用例通過來,第二個用例運行是js出現了報錯。此時便能根據測試結果,調整代碼
更多測試方法此處不做討論,具體可以參考Jest文檔
在具體項目中的使用
下面來在實際的項目中使用Jest + Enzyme來進行測試。測試Demo項目
首先,使用Create-React-App
來創建一個應用。
接着,安裝jest
npm install --save-dev jest
由於在書寫用例時,會用到es6語法,所以還要安裝babel-jest
來進行轉碼
npm install --save-dev babel-jest
安裝enzyme
npm install --save-dev enzyme
也可以使用react官方測試插件react-addons-test-utils
,此處我們使用enzyme,故不需要安裝。
此外,還需要根據使用的react版本來安裝enzyme-adapter-react
。具體版本對照如下
enzyme-adapter-react版本 | react版本 | ||
---|---|---|---|
enzyme-adapter-react-16 |
^16.4.0-0 |
||
enzyme-adapter-react-16.3 |
~16.3.0-0 |
||
enzyme-adapter-react-16.2 |
~16.2 |
||
enzyme-adapter-react-16.1 |
`~16.0.0-0 \ | \ | ~16.1` |
enzyme-adapter-react-15 |
^15.5.0 |
||
enzyme-adapter-react-15.4 |
15.0.0-0 - 15.4.x |
||
enzyme-adapter-react-14 |
^0.14.0 |
||
enzyme-adapter-react-13 |
^0.13.0 |
此處demo使用的react
版本爲^16.4.1
,所以我們需要安裝enzyme-adapter-react-16
npm install --save-dev enzyme-adapter-react-16
依賴安裝完成,接下來需要進行相關的配置。
首先配置package.json的測試命令test: "jest"
。
此時如果我們在根目錄下創建一個.test.js
文件,並書寫簡單的方法用例,執行測試命令,是可以正常執行測試用例的。但是,我們的項目卻並不是簡單的單個方法但測試,實際項目中會存在這大量的組件依賴,還有css
,image
等靜態資源的處理。所以,還要進行如下配置處理。
首先,我們在package.json
文件中新增一個jest
的配置項
jest: {}
這裏我們主要進行三個配置。
-
moduleFileExtensions
代表支持加載的文件名。此處我們的測試文件均以.js
結尾,所以只配置成["js"]
即可 -
transform
用於編譯 ES6/ES7 語法,需配合 babel-jest 使用 -
moduleNameMapper
代表需要被 Mock 的資源名稱。如果需要 Mock 靜態資源(如less、scss等),則需要配置 Mock 的路徑
jest默認會檢索項目內的*.test.js
,*.test.jsx
形式的文件並執行。當編寫當用例沒被jest檢索到時,可通過moduleDirectories
來配置路徑。
在具體到組件測試時,爲了測試組件到交互性,我們需要jest渲染出組件進行操作,此時,由於我們到項目中大量使用來webpack
到依賴管理,以及less-loader
、url-loader
等預編譯。在jest渲染組件是,無法識別這些.less
等文件。所以我們需要通過mock
來處理這些靜態文件。因爲jest在渲染組件時,是不需要依賴css
,image
等靜態資源的。所以我們可以這樣配置:
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
前面通過正則來適配我們需要匹配的靜態文件,後面爲我們通過mock返回的數據。這裏我們還需要在根目錄中創建__mock__
的文件夾。在裏面新建fileMock.js
和styleMock.js
兩個文件。
<!--fileMock.js-->
module.exports = 'test-file-stub';
<!--styleMock.js-->
module.exports = {};
這樣就可以將測試集中在組件的結構和邏輯上。另外,可能在我們的項目中,會使用大量的別名來簡化引用路徑,及webpack中的alias
配置。此處同樣需要進行別名的配置,配置方式與靜態資源配置類似。一下是完整配置
"jest": {
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleDirectories": [
"src",
"node_modules"
],
"transform": {
"^.+\\.js$": "babel-jest"
},
"moduleNameMapper": {
"^components(.*)$": "<rootDir>/src/components$1",
"^pages(.*)$": "<rootDir>/src/pages$1",
"^utils(.*)$": "<rootDir>/src/utils$1",
"^services(.*)$": "<rootDir>/src/services$1",
"^static(.*)$": "<rootDir>/src/static$1",
"^models(.*)$": "<rootDir>/src/models$1",
"^variable(.*)$": "<rootDir>//src/static/less/variable.less",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
}
}
接下來創建一個待測試的組件,在src > pages
文件夾中創建login
組件,並配置好路由。組件代碼參考測試Demo項目
運行後頁面如下
<img src="https://github.com/ISummerRai...; width="320" />
接着,定義測試用例,此Demo定義來八個測試用例如下
- 1、頁面title顯示“登錄”(UI)
- 2、登錄賬號輸入手機號或郵箱時,賬號上方顯示登錄賬號
- 3、登錄賬號輸入不爲手機號或郵箱,賬號上方顯示【賬戶輸入錯誤,請重新輸入】
- 4、賬號輸入正常,密碼小於6位,登錄按鈕置灰。
- 5、賬號輸入異常,密碼不小於6位,登錄按鈕置灰。
- 6、賬號輸入正常,密碼不小於6位,登錄按鈕可點。
- 7、點擊密碼後眼睛圖標,顯示密碼。
- 8、顯示密碼狀態,再次點擊,隱藏密碼。
接下來,新建文件login.test.js
來編寫測試用例代碼。
由於用例中設計多個交互,所以我們需要先渲染出組件。Enzyme爲我們提供來三種渲染組件的方法shallow
、render
、mount
。
-
shallow
方法就是官方的shallow rendering的封裝。 -
render
方法將React組件渲染成靜態的HTML字符串,然後分析這段HTML代碼的結構,返回一個對象。它跟shallow方法非常像,主要的不同是採用了第三方HTML解析庫Cheerio,它返回的是一個Cheerio實例對象。 -
mount
方法用於將React組件加載爲真實DOM節點。
三種方法中,shallow
render
返回的爲對象,用於分析HTML結構,所以無法用於交互測試。mount
方法加載的爲真實的DOM節點,所以可用於交互測試。本Login
組件存在大量交互測試,所以使用mount
創建組件,使用mount
需要先使用Adapter
配置如下
import Login from 'pages/Login';
import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { mount } from 'enzyme';
configure({ adapter: new Adapter() });
const wrapper = mount(<Login />);
現在,我們就可以使用Enzyme
的API來編寫測試用例了,Enzyme
提供了豐富的類jquery
風格的API,下面是部分API
.get(index):返回指定位置的子組件的DOM節點
.at(index):返回指定位置的子組件
.first():返回第一個子組件
.last():返回最後一個子組件
.type():返回當前組件的類型
.text():返回當前組件的文本內容
.html():返回當前組件的HTML代碼形式
.props():返回根組件的所有屬性
.prop(key):返回根組件的指定屬性
.state([key]):返回根組件的狀態
.setState(nextState):設置根組件的狀態
.setProps(nextProps):設置根組件的屬性
完整API參見 Enzyme API
在前半部分的demo中,我們使用來 test()
方法來編寫用例,此處,我們使用
describe('', () => {
it('', () => {})
})
來編寫測試用例,這樣我們可以對測試用例進行分組
讓我們來開始第一個用例“頁面title顯示「登錄」”的編寫
it('標題顯示', () => {
const title = wrapper.find('.title').text();
expect(title).toBe('登錄');
})
這個用例十分簡單,僅僅在第一步獲取到了title
中的文本,並對文本進行校驗。
第二個和第三個用例爲對輸入框輸入文本對校驗,此處,我們可以單獨對校驗方法進行測試,也可以頁面對交互來完成測試。這裏用例通過交互來進行測試用例對編寫。由於在輸入信息過程中,校驗通過input
框的onChange
事件觸發,所以我們需要用到 simulate
來觸發事件。其中一個用例如下
const accountInput = wrapper.find('.account').find('input');
const accountTitle = wrapper.find('.account .name').find('span');
it('輸入不合法賬號', () => {
const event = {
target: {
value: 'abc123'
}
}
accountInput.simulate('change', event);
expect(accountTitle.text()).toBe('賬戶輸入錯誤,請重新輸入');
})
模擬輸入來一個不合法的賬號‘abc123’,驗證失敗,顯示失敗信息。
在4,5,6三個用例中,需要獲取登錄按鈕Button
組件的可點擊狀態,由於enzyme
無法獲取 css
狀態,此時可以使用API中的prop(key)
來獲取組件的props狀態,從而判斷組件的可點擊狀態。其中一個用例如下
it('輸入正確賬號,密碼小於6位,指定狀態', () => {
wrapper.setState({
account: '18888888888',
password: '12345',
errorAccount: false
});
// 此處需重新獲取btn對象,否則會導致用例失敗
const submitBtn = wrapper.find('.btn-box').find('Button');
expect(submitBtn.props().disabled).toBe(true);
})
此處通過直接設置state
的值來更改Button的狀態。需要注意的是,爲來減少重複定義,許多Dom對象的獲取都在describe
組下做了統一的定義,但在執行expect
獲取按鈕狀態是,需要重新查找,來獲取最新但狀態。除了直接指定state
狀態之外,還可以通過輸入框輸入,change事件觸發但方式來完成用例,如下
it('輸入正確賬號,密碼小於6位,通過change觸發', () => {
const accountEvent = {
target: {
value: '18888888888'
}
};
const pwdEvent = {
target: {
value: '12345'
}
}
accountInput.simulate('change', accountEvent);
passwordInput.simulate('change', pwdEvent);
const submitBtn = wrapper.find('.btn-box').find('Button');
expect(submitBtn.prop('disabled')).toBe(true);
7、8兩個用例使用但方法與上面相同,不再贅述。
所有用例編寫完成之後,執行npm run test
可以看到所有用例都通過測試。
測試覆蓋率
在 package.json
文件的 test
命令修改爲
test: "jest --coverage"
執行 npm run test
即可在用例執行信息後顯示用例的覆蓋率報告。