jest 是 Facebook 推出的測試工具,enzyme 是airbnb 推出的 React 測試類庫,使用二者能夠很好地測試 React 組件。react
首先安裝對應的依賴:git
npm i -D jest babel-jest @babel/core @babel/preset-env @babel/preset-react
複製代碼
其中 babel-jest
是自動使用 babel 編譯文件。github
安裝 enzyme 相關的依賴:npm
npm i -D enzyme enzyme-adapter-react-16 jest-environment-enzyme
複製代碼
配置 enzyme,新建文件 setupEnzyme.js
,寫入如下內容:json
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
Enzyme.configure({
adapter: new Adapter()
})
複製代碼
配置相應的 jest,redux
{
"setupFilesAfterEnv": [
"./setupEnzyme.js"
],
"testEnvironment": "enzyme"
}
複製代碼
配置對應的 babel:bash
{
"presets": [
["@babel/preset-env"],
["@babel/preset-react"]
]
}
複製代碼
配置 npm scripts:babel
"scripts": {
"test": "jest --config=jest.config.json"
},
複製代碼
以一個最簡單的 list 組件爲例:app
import React from 'react'
export default ({list}) => {
return <ul> { list.map(item => item > 5 && <li className="item" key={item}>{item}</li>) } </ul>
}
複製代碼
好比,傳遞一組 list
數據 [1,2,3,4,5,6]
那麼這個組件應該是渲染 1 個 list item,而若是傳遞數據 [6,7,8,9]
則應該是渲染 4 個 list item,以下:ide
import React from 'react'
import { render } from 'enzyme'
import List from './index'
describe('<List/>', () => {
it('render 1 child', () => {
const wrapper = render(<List list={[1,2,3,4,5,6]} />)
expect(wrapper.find('.item').length).toBe(1)
})
it('render 4 child', () => {
const wrapper = render(<List list={[6,7,8,9]} />)
expect(wrapper.find('.item').length).toBe(4)
})
})
複製代碼
運行測試命令就能夠看到測試經過的界面。
使用 Redux 時,connected 組件一種簡單的單元測試方式就是將 plain 組件也 export,以下:
import React from 'react'
import { connect } from 'react-redux'
export const List = ({list}) => {
return <ul> { list.map(item => item > 5 && <li className="item" key={item}>{item}</li>) } </ul>
}
export default connect((state) =>({list:state.list}))(List)
複製代碼
這樣就能夠像以前的測試同樣測試了。
還有一種方式就是使用 redux-mock-store
,安裝完成後,以一個簡單的例子,以下:
import React from 'react'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
import { Provider } from 'react-redux'
import List from './index'
const mockStore = configureStore([])
describe('<List/>', () => {
it('render 0 child', () => {
const store = mockStore({
list: []
})
const wrapper = mount(<Provider store={store}><List/></Provider>)
expect(wrapper.find('.item').length).toBe(0)
})
it('render 2 child', () => {
const store = mockStore({
list: ['111','222']
})
const wrapper = mount(<Provider store={store}><List/></Provider>)
expect(wrapper.find('.item').length).toBe(2)
})
})
複製代碼
這裏須要注意的有兩點:
redux-mock-store
只是爲了測試 actions 相關的邏輯,不會自動更新 store,(因此上面的第二個測試沒有直接使用 store.dispatch
,而是從新 mock 了數據,由於做者認爲 reducer 就是純函數,純函數怎麼測試就怎麼測試,see github.com/arnaudbenar…)mount
而不是使用 shallow
由於 shallow
只渲染當前組件,只能能對當前組件作斷言;mount
會渲染當前組件以及全部子組件,顯然上面是須要使用 mount
的單元測試須要明確的一點是不該該去測試實現的邏輯,而是應該關注輸入輸出,而 state 的改變基本上最終都會體如今 UI 的改變,因此關注 UI 的改變便可,如下例子,點擊按鈕後,state 內容改變:
import React, { useState } from 'react'
const Add = () => {
const [text, setText] = useState('hello')
return (
<div> <button onClick={() => setText(text === 'hello' ? 'world' : 'hello')}>change</button> <p>{text}</p> </div>
)
}
export default Add
複製代碼
那麼他的測試應該是測試點擊後文字內容是否更改,以下:
import React from 'react'
import { shallow } from 'enzyme'
import Add from './index'
describe('<Add/>', () => {
it('click', () => {
const wrapper = shallow(<Add/>)
expect(wrapper.find('p').text()).toBe('hello')
wrapper.find('button').simulate('click')
expect(wrapper.find('p').text()).toBe('world')
wrapper.find('button').simulate('click')
expect(wrapper.find('p').text()).toBe('hello')
})
})
複製代碼
對於用戶界面的操做, enzyme 能夠經過 simulate
來模擬交互事件,以一個簡單的例子爲例,用戶點擊按鈕後觸發事件更新store:
import React from 'react'
import { mount } from 'enzyme'
import { Provider } from 'react-redux'
import configureStore from 'redux-mock-store'
import Add from './index'
const mockStore = configureStore([])
describe('<Add/>', () => {
it('click', () => {
const store = mockStore({
list: []
})
const wrapper = mount(<Provider store={store}><Add/></Provider>)
wrapper.find('button').simulate('click')
wrapper.find('button').simulate('click')
expect(store.getActions().length).toBe(2)
})
})
複製代碼
這裏利用的就是用戶點擊兩次後將觸發兩次 dispatch,經過 store.getActions
來判斷是否觸發 dispatch,以及 dispatch 的次數。
snapshot 快照測試第一次運行的時候會將 React 組件在不一樣狀況下的渲染結果保存一份快照文件。後面每次運行快照測試的時候,都會和第一次比較,想要生成新的快照文件添加 -u
參數生成新的快照文件。快照文件是以 .snap
結尾的文件,會在運行測試的時候存放在 __snapshots__
文件夾下。
snapshot 測試 jest 提供了 react-test-renderer
, enzyme 提供的 render
進行了封裝,同時提供了 enzyme-to-json
幫助將 wrapper
與快照文件進行對比,以一個簡單的例子爲例:
it('basic use', () => {
const text = ['12', '13']
const wrapper = render(
<List list={text} />
)
expect(toJson(wrapper)).toMatchSnapshot()
})
it('without item', () => {
const wrapper = render(
<List list={[]}/>
)
expect(toJson(wrapper)).toMatchSnapshot()
})
複製代碼
第一次運行測試後會生成對應快照文件:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<List/> basic use 1`] = ` <ul> <li class="item" > 12 </li> <li class="item" > 13 </li> </ul> `;
exports[`<List/> without item 1`] = `<ul />`;
複製代碼
修改組件後,從新測試則會報錯:
若是你肯定此次的修改是符合你預期的,那麼你應該從新生成快照文件。
快照文件應該被 git 提交跟蹤嗎?固然,快照文件應該是要和代碼一併提交和 review 的。
快照文件更新問題,若是一段時間大量的修改了不少 UI 組件,這個時候控制檯就會有不少錯誤,這個時候須要單獨看每一個組件是否符合咱們的更改,符合的話就須要從新生成快照, -u
參數是會默認更新全部的快照的,若是隻是想要更新部分,可使用 --testNamePattern
參數。
固然開發的時候應該是儘量多提交 git,而且把單測放在 git hooks 上,儘可能避免須要一次審查過多文件的狀況。
遷移到 Typescript 後將 babel-jest 替換爲 ts-jest 便可。
npm i ts-jest -D
複製代碼
參考:
最後照舊是一個廣告貼,最近新開了一個分享技術的公衆號,歡迎你們關注👇(目前關注人數可憐🤕)