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的测试方法。固然,你们在平时写程序的过程当中,也要考虑到如何让测试更方便更简洁合理,而不是只为了实现功能而写代码。

相关文章
相关标签/搜索