爲了確保軟件質量,保證函數邏輯的正確性,咱們通常會進行單元測試。本文主要講述了在基於dva框架的應用中,咱們是如何對model中的reducer
和effect
進行單元測試的,以及背後的一點原理。react
這是一套由國內開發團隊開發的輕量級數據流框架,集成了當前流行JavaScript庫好比redux,react-router和redux-saga,易學易用,使開發中的數據流管理變得更加簡單高效。數據庫
因爲reducer都是純函數,所以,只要給定一個payload,就會有肯定的輸出,不會因函數外部其餘環境影響而改變輸出結果。redux
例如,我有個reducer函數saveNotify
,其實現了將payload
中的notification
信息更新到state
中:bash
saveNotify(state, { payload }) {
let {notification} = state
if (!payload) {
return state
}
notification = {...notification, ...payload.notification}
notification.visible = true
state.notification = notification
}
複製代碼
值得注意的是,咱們能夠在.umirc.js
文件中配置umi-plugin-react的屬性,設置dva開啓immer
爲true
,這樣就能夠保持原始的state的不變性。react-router
[
'umi-plugin-react',
{
dva: {
immer: true
},
}
]
複製代碼
這就是爲何咱們能夠直接操做傳入的state,改變其值,由於此時傳入的state並非最原始的state,而是一個Proxy
對象,當咱們改變它的值,immer會自動和初始state作merge
的操做。以下圖所示:app
const payload = {title:'Create Role Successfully.', status:'successful'}
const initState = {notification: {}}
describe('Notification Models:', () => {
it('should save Notify payload:', () => {
const saveNotify = AppModule.reducers.saveNotify
const state = initState
const result = saveNotify(state, { payload : {notification: payload} })
expect(state.notification.status).toEqual('successful')
expect(state.notification.visible).toEqual(true)
})
}}
複製代碼
effects雖然不是純函數,會涉及諸如API服務調用,讀取文件和數據庫的操做等,但因爲在單元測試中,也就是說在這麼一個函數中,咱們並不須要去關心其調用API的過程,只要關心咱們的函數是否有發起API請求便可。在後續邏輯中,須要用到調API返回的結果,那麼咱們能夠直接給它模擬一個結果傳入。框架
例如,我有這麼一個effect函數,其做用是發起createInfo
的API請求,而後根據reponse
的結果來實行不一樣的操做。當返回結果的success
爲true
,即沒有error時,進行頁面跳轉而且執行put
操做改變state中的notification狀態,彈出notification消息框。固然,我這裏省略了出現error的狀況處理。異步
*createInfo({ payload: { values } }, { call, put }) {
const { data, success } = yield call(Service.createInfo, values)
if (data && success) {
const { id } = data
router.push(`/users/${id}/view`);
const notification = {title:'Create information Successfully.', status:'successful'}
yield put({ type: 'app/notify', payload:{notification}})
}
}
複製代碼
effect函數實際上是一個generator
函數,不少人覺得寫effect測試只需調用.next()
便可,但卻未深究爲何要這麼作。函數
在ES6中新添了generator
函數,generator
函數和普通函數的差異即爲:它是可中途中止執行的函數。它是一個解決異步請求的很好的方案。每遇到yield
關鍵字,它就會自動暫停,直到咱們手動去讓它繼續開始。dav封裝了redux-saga,那麼redux-saga的Effects管理機制會自行來啓動開始讓函數繼續運行。而在測試中咱們則須要調用.next()
手動啓動繼續運行。單元測試
初始化:首先咱們須要初始化generator
函數,此時並無開啓運行,因此這一步在createInfo
這個effect函數中什麼也沒有發生。
const actionCreator = {
type: 'info/createInfo',
payload: {
values: {
name: 'abc',
description: 'xxx',
}
}
}
const generator = info.effects.createInfo(actionCreator, { call, put })
複製代碼
開始執行: 咱們調用generator.next()
會啓動函數的執行,函數會在遇到yield
關鍵字時中止,這時候尚未去發起調用API服務,只是準備去發起。調用.next()
會返回一個對象:
{ value: xxxx, done: false}
複製代碼
value表示的是yield
接下來該作的事,即call API這個行爲。
let next = generator.next()
expect(next.value).toEqual(call(Service.createInfo, actionCreator.payload.values))
複製代碼
繼續運行:咱們再接着調用.next()
啓動運行,在這一步函數會真正地去執行call(Service.createInfo, actionCreator.payload.values)
。 拿到結果後,進入到if語句,直到遇到下一個yield
關鍵字而暫停。 因爲執行call會返回一個response
執行結果,在單元測試中咱們就須要在調用.next()
時傳入一個模擬的response
:
next = generator.next({
success: true,
data: { id: '123456' }
})
複製代碼
這個時候函數已經執行完獲取response
中id
的操做而且進行router跳轉,且又在遇到下一個yield
關鍵字時暫停。這時候咱們能夠斷言mock的router.push
有沒有執行,而且判斷當前next
的value是否爲put
操做:
router.push = jest.fn()
expect(router.push).toHaveBeenCalledWith(`/list/123456/view`)
const notification = {title:'Create Information Successfully.', status:'successful'}
expect(next.value).toEqual(put({ type: 'app/notify', payload:{notification}}))
複製代碼
當咱們再次調用.next()
讓其繼續運行的時候,接下來的操做已經沒有yield
關鍵詞了,所以函數會一直執行直到結束,而此時的value也會是undefined
:
next=generator.next()
expect(next.value).toBeUndefined()
複製代碼
但願你們能經過個人小例子不只能初步學習dva框架的model中reducer和effect函數的測試流程,也能理解effect函數的執行過程以及saga的測試方法。固然,你們在平時寫程序的過程當中,也要考慮到如何讓測試更方便更簡潔合理,而不是隻爲了實現功能而寫代碼。