如果你想學習 React 單元測試,那就從這篇文章開始吧。Star 專案,clone 到本地,根據教程走一遍,有任何問題歡迎 issue 討論。
專案GitHub地址:react-test-demo
文章主要內容如下:
- Jest 和 Enzyme 的基本介紹
- 測試環境搭建
- 測試指令碼編寫
- UI 元件測試
- Reducer 測試
- 執行並除錯
- 參考資料
Jest、Enzyme 介紹
Jest 是 Facebook 釋出的一個開源的、基於 Jasmine
框架的 JavaScript 單元測試工具。提供了包括內建的測試環境 DOM API 支援、斷言庫、Mock 庫等,還包含了 Spapshot Testing、 Instant Feedback 等特性。
Airbnb開源的 React 測試類庫 Enzyme 提供了一套簡潔強大的 API,並通過 jQuery 風格的方式進行DOM 處理,開發體驗十分友好。不僅在開源社群有超高人氣,同時也獲得了React 官方的推薦。
測試環境搭建
在開發 React 應用的基礎上(預設你用的是 Webpack + Babel 來打包構建應用),你需要安裝 Jest
Enzyme
,以及對應的 babel-jest
npm install jest enzyme babel-jest --save-dev
複製程式碼
下載 npm 依賴包之後,你需要在 package.json
中新增屬性,配置 Jest:
"jest": {
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
".*\\.(css|less|scss)$": "<rootDir>/__mocks__/styleMock.js"
},
"transform": {
"^.+\\.js$": "babel-jest"
}
},
複製程式碼
並新增test scripts
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --inline --progress --colors --port 3000 --host 0.0.0.0 ",
"test": "jest"
}
複製程式碼
其中 :
moduleFileExtensions
:代表支援載入的檔名,與 Webpack 中的resolve.extensions
類似moduleNameMapper
:代表需要被 Mock 的資源名稱。如果需要 Mock 靜態資源(如less、scss等),則需要配置 Mock 的路徑<rootDir>/__mocks__/yourMock.js
transform
用於編譯 ES6/ES7 語法,需配合babel-jest
使用
上面三個是常用的配置,更多 Jest 配置見官方文件:Jest Configuration
測試指令碼編寫
UI 元件測試
環境搭建好了,就可以開始動手寫測試指令碼了。在開始之前,先分析下 Todo 應用的組成部分。
應用主體結構如下 src/component/App.js
:
class App extends Component {
render() {
const { params } = this.props;
return (
<section className="todoapp">
<div className="main">
<AddTodo />
<VisibleTodoList filter={params.filter || 'all'} />
</div>
<Footer />
</section>
)
}
}
複製程式碼
可以發現 整個應用可以分為三個元件:
- 最外層的
<App />
- 中間的 Input 輸入框
<AddTodo />
- 下面的 TODO 列表
<VisibleTodoList />
其中 <App/>
是 UI 元件,<AddTodo />
和 <VisibleTodoList />
是智慧元件,我們需要找到智慧元件所對應的 UI 元件 <AddTodoView/>
和 <TodoList/>
。
<AddTodoView/>
就是一個 Input
輸入框,接受文字輸入,敲下Enter鍵,建立一個 Todo。程式碼如下 src/component/AddTodoView.js
:
import React, { Component, PropTypes } from 'react'
class AddTodoView extends Component {
render() {
return (
<header className="header">
<h1>todos</h1>
<input
className="new-todo"
type="text"
onKeyUp={e => this.handleClick(e)}
placeholder="input todo item"
ref='input' />
</header>
)
}
handleClick(e) {
if (e.keyCode === 13) {
const node = this.refs.input;
const text = node.value.trim();
text && this.props.onAddClick(text);
node.value = '';
}
}
}
複製程式碼
瞭解了該元件的功能之後,我們首先需要明確該元件需要測試哪些點:
- 元件是否正常渲染
- 當使用者輸入內容敲下Enter鍵時,是否能正常的呼叫
props
傳遞的onAddClick(text)
方法 - 建立完成後清除 Input 的值
- 當使用者沒有輸入任何值時,敲下回車時,應該不呼叫
props
傳遞的onAddClick(text)
方法
經過上面的分析之後,我們就可以開始編寫單元測試指令碼了。
第一步:引入相關 lib
import React from 'react'
import App from '../../src/component/App'
import { shallow } from 'enzyme'
複製程式碼
在這裡我們引入了 shallow
方法,它是 Enzyme
提供的 API 之一,可以實現淺渲染。其作用是僅僅渲染至虛擬節點,不會返回真實的節點,能極大提高測試效能。但是它不適合測試包含子元件、需要測試宣告週期的元件。
Enzyme
還提供了其他兩個 API:
mount
:Full Rendering,非常適用於存在於 DOM API 存在互動元件,或者需要測試元件完整的宣告週期render
:Static Rendering,用於 將 React 元件渲染成靜態的 HTML 並分析生成的 HTML 結構。render
返回的wrapper
與其他兩個 API 類似。不同的是render
使用了第三方 HTML 解析器和Cheerio
。
一般情況下,shallow
就已經足夠用了,偶爾情況下會用到 mount
。
第二步:模擬 Props,渲染元件建立 Wrapper
這一步,我們可以建立一個 setup
函式來實現。
const setup = () => {
// 模擬 props
const props = {
// Jest 提供的mock 函式
onAddClick: jest.fn()
}
// 通過 enzyme 提供的 shallow(淺渲染) 建立元件
const wrapper = shallow(<AddTodoView {...props} />)
return {
props,
wrapper
}
}
複製程式碼
Props
中包含函式的時候,我們需要使用 Jest 提供的 mockFunction
第四步:編寫 Test Case
這裡的 Case 根據我們前面分析需要測試的點編寫。
Case1:測試元件是否正常渲染
describe('AddTodoView', () => {
const { wrapper, props } = setup();
// case1
// 通過查詢存在 Input,測試元件正常渲染
it('AddTodoView Component should be render', () => {
//.find(selector) 是 Enzyme shallow Rendering 提供的語法, 用於查詢節點
// 詳細用法見 Enzyme 文件 http://airbnb.io/enzyme/docs/api/shallow.html
expect(wrapper.find('input').exists());
})
})
複製程式碼
寫完第一個測試用例之後,我們可以執行看看測試的效果。在 Terminal 中輸入 npm run test
,效果如下:
Case2: 輸入內容並敲下Enter鍵,測試元件呼叫props的方法
it('When the Enter key was pressed, onAddClick() shoule be called', () => {
// mock input 輸入和 Enter事件
const mockEvent = {
keyCode: 13, // enter 事件
target: {
value: 'Test'
}
}
// 通過 Enzyme 提供的 simulate api 模擬 DOM 事件
wrapper.find('input').simulate('keyup',mockEvent)
// 判斷 props.onAddClick 是否被呼叫
expect(props.onAddClick).toBeCalled()
})
複製程式碼
上面的程式碼與第一個 case 多了兩點:
- 增加了
mockEvent
,用於模擬 DOM 事件 - 使用
Enzyme
提供的.simulate(’keyup‘, mockEvent)
來模擬點選事件,這裡的keyup
會自動轉換成 React 元件中的onKeyUp
並呼叫。
我們再執行 npm run test
看看測試效果:
經過上面兩個 Test Case 的分析,接下來的 Case3 和 Case4 思路也是一樣,具體寫法見程式碼: test/component/AddTodoView.spec.js,這裡就不一一講解了。
Reducer 測試
由於 Reducer 是純函式,因此對 Reducer 的測試非常簡單,Redux 官方文件也提供了測試的例子,程式碼如下:
import reducer from '../../reducers/todos'
import * as types from '../../constants/ActionTypes'
describe('todos reducer', () => {
it('should return the initial state', () => {
expect(
reducer(undefined, {})
).toEqual([
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
it('should handle ADD_TODO', () => {
expect(
reducer([], {
type: types.ADD_TODO,
text: 'Run the tests'
})
).toEqual(
[
{
text: 'Run the tests',
completed: false,
id: 0
}
]
)
expect(
reducer(
[
{
text: 'Use Redux',
completed: false,
id: 0
}
],
{
type: types.ADD_TODO,
text: 'Run the tests'
}
)
).toEqual(
[
{
text: 'Run the tests',
completed: false,
id: 1
},
{
text: 'Use Redux',
completed: false,
id: 0
}
]
)
})
})
複製程式碼
更多關於 Redux 的測試可以看官網提供的例子:編寫測試-Redux文件
除錯及測試覆蓋率報告
在執行測試指令碼過程,Jest
的錯誤提示資訊友好,通過錯誤資訊一般都能找到問題的所在。
同時 Jest
還提供了生成測試覆蓋率報告的命令,只需要新增上 --coverage
這個引數既可生成。不僅會在終端中顯示:
而且還會在專案中生成 coverage
資料夾,非常方便。
資料
掘金技術徵文第三期:聊聊你的最佳實踐:https://juejin.im/post/58d8e99261ff4b006cd6874d