測試是應用生產過程當中不可缺乏的一個環節,開發人員在編碼時總有考慮不周全或者出錯的狀況,而測試則是經過對比實際結果與預期結果來找出問題和缺陷,從而確保軟件的質量。本文主要介紹了在最近在工做中用Jest
和Enzyme
來測試React 組件的過程和容易踩坑的地方。api
對於一個Web網站來講,測試的種類主要分爲如下3種:瀏覽器
市面上如今有不少測試工具,公司裏採用Umijs做爲腳手架快速搭建了一個React應用,而Umi內部採用了Dva做爲數據流管理,同時也自動配置了Jest測試框架。bash
Jest
測試框架由Facebook所推薦,其優勢是運行快性能好,而且涵蓋了測試所需的多種功能模塊(包括斷言,模擬函數,比較組件的快照snapshot,運行測試,生成測試結果和覆蓋率等),配置簡單方便,適合大項目的快速測試。app
測試React組件咱們採用Enzyme
工具庫,它提供3種組件渲染方式:框架
Shallow
:不會渲染子組件Mount
: 渲染子組件,同時包含生命週期函數如componentDidMount
Render
: 渲染子組件,但不會包含生命週期,同時可用的API也會減小好比setState()
通常狀況下用shallow和mount的狀況比較多。dom
有些組件被Connect
包裹起來,這種狀況不能直接測,須要創建一個Provider
和傳入一個store
,這種過程比較痛苦,最好是將去掉Connect
後的組件 export
出來單獨測,採用shallow
的渲染方法,僅測該組件的邏輯。ide
例如被測的組件以下:函數
export class Dialog extends Component {
...
}
export default connect(mapStateToProps, mapDispatch)(Dialog)
複製代碼
那麼在測試文件中, 能夠這樣初始化一個控件:工具
import {Dialog} from '../dialog'
function setup(propOverrides) {
const props = Object.assign(
{
state:{}
actions:{},
},
propOverrides,
)
const enzymeWrapper = shallow(<Dialog {...props} />)
return {
props,
enzymeWrapper,
}
}
複製代碼
有的組件,須要測試和原生DOM元素的交互,好比要測點擊原生button元素,是否觸發當前的組件的事件,或者須要測試和子組件的交互時,這時候用須要用mount來渲染。post
例如,個人Editor組件是這樣:
export default class Editor extends Component {
constructor(props) {
super(props)
this.state = {
onClickBtn: null,
}
}
handleSubmit = ({ values, setSubmitting }) => {
const { onClickBtn } = this.state
this.props.actions.createInfo(values, onClickBtn)
}
handleCancel = () => {
...
}
setOnClickBtn(name) {
this.setState({
onClickBtn: name,
})
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
{({ handleChange }) => {
return (
<div className="information-form">
<Input name={FIELD_ROLE_NAME} onChange={handleChange}
/>
<Input name={FIELD_ROLE_KEY} onChange={handleChange}
/>
<div>
<Button type="button" onClick={this.handleCancel}> Cancel </Button>
<Button type="submit" primary onClick={() => this.setOnClickBtn('create')} > Create </Button>
<Button type="submit" primary onClick={() => this.setOnClickBtn('continue')} > Create and Continue </Button>}
</div>
</div>
)
}}
</Form>
)
}
}
複製代碼
此時Form的children是個function
,要測試表單中按鈕點擊事件,若是隻用shallow,是沒法找到Form中children的元素的,所以這裏採用mount
方式將整個dom渲染,可直接模擬type爲submit屬性的那個button的點擊事件。 而後測試點擊該button是否完成了2個事件:handleSubmit
和setOnclickBtn
。
有人會想到模擬form的submit
事件,但在mount的狀況下,模擬button的click
事件一樣能夠觸發onSubmit事件。
因爲submit過程要涉及子控件的交互,其過程具備必定的不肯定性,此時須要設置一個timeout
,延長一段時間再來判斷submit內的action是否被執行。
it('should call create role action when click save', () => {
const preProps = {
actions: {
createInfo: jest.fn(),
}
}
const { props, enzymeWrapper } = setup(preProps)
const nameInput = enzymeWrapper.find('input').at(0)
nameInput.simulate('change', { target: { value: 'RoleName' } })
const keyInput = enzymeWrapper.find('input').at(1)
keyInput.simulate('change', { target: { value: 'RoleKey' } })
const saveButton = enzymeWrapper.find('button[type="submit"]').at(0)
saveButton.simulate('click')
expect(enzymeWrapper.state().onClickBtn).toBe('save')
setTimeout(() => {
expect(props.actions.createInfo).toHaveBeenCalled()
}, 500)
})
複製代碼
可是用mount
來渲染也有容易讓人失誤的地方,好比說要找到子組件,可能須要多層.children()
才能找到。在單元測試中,應儘可能採用shallow
渲染,測試粒度儘量減少。
有的組件的函數邏輯中會含有Promise
,其返回結果帶有不肯定性,例如如下代碼段中的auth.handleAuthenticateResponse
,傳入的參數是一個callback函數,須要根據auth.handleAuthenticateResponse
的處理結果是error
仍是正常的result
來處理本身的內部邏輯。
handleAuthentication = () => {
const { location, auth } = this.props
if (/access_token|id_token|error/.test(location.search)) {
auth.handleAuthenticateResponse(this.handleResponse)
}
}
handleResponse = (error, result) => {
const { auth } = this.props
let postMessageBody = null
if (error) {
postMessageBody = error
} else {
auth.setSession(result)
postMessageBody = result
}
this.handleLogicWithState(postMessageBody)
}
複製代碼
在測試時,可用jest.fn()模擬出auth.handleAuthenticateResponse
函數,同時讓它返回一個肯定的結果。
const preProps = {
auth: {
handleAuthenticateResponse: jest.fn(cb => cb(errorMsg))
}
}
setup(preProps)
複製代碼
enzyme: airbnb.io/enzyme/
Jest: jestjs.io/docs/en/api