dva應用中reducers和effects的單元測試實戰

前言

爲了確保軟件質量,保證函數邏輯的正確性,咱們通常會進行單元測試。本文主要講述了在基於dva框架的應用中,咱們是如何對model中的reducereffect進行單元測試的,以及背後的一點原理。react

dva框架

這是一套由國內開發團隊開發的輕量級數據流框架,集成了當前流行JavaScript庫好比redux,react-router和redux-saga,易學易用,使開發中的數據流管理變得更加簡單高效。數據庫

測試reducers

因爲reducer都是純函數,所以,只要給定一個payload,就會有肯定的輸出,不會因函數外部其餘環境影響而改變輸出結果。redux

reducer函數樣例

例如,我有個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開啓immertrue,這樣就能夠保持原始的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

effects雖然不是純函數,會涉及諸如API服務調用,讀取文件和數據庫的操做等,但因爲在單元測試中,也就是說在這麼一個函數中,咱們並不須要去關心其調用API的過程,只要關心咱們的函數是否有發起API請求便可。在後續邏輯中,須要用到調API返回的結果,那麼咱們能夠直接給它模擬一個結果傳入。框架

effect函數樣例

例如,我有這麼一個effect函數,其做用是發起createInfo的API請求,而後根據reponse的結果來實行不一樣的操做。當返回結果的successtrue,即沒有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' }
})
複製代碼

這個時候函數已經執行完獲取responseid的操做而且進行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的測試方法。固然,你們在平時寫程序的過程當中,也要考慮到如何讓測試更方便更簡潔合理,而不是隻爲了實現功能而寫代碼。

相關文章
相關標籤/搜索