dva应用中reducers和effects的单元测试实战

前言

为了确保软件质量,保证函数逻辑的正确性,我们一般会进行单元测试。本文主要讲述了在基于dva框架的应用中,我们是如何对model中的reducereffect进行单元测试的,以及背后的一点原理。

dva框架

这是一套由国内开发团队开发的轻量级数据流框架,集成了当前流行JavaScript库比如redux,react-router和redux-saga,易学易用,使开发中的数据流管理变得更加简单高效。

测试reducers

由于reducer都是纯函数,因此,只要给定一个payload,就会有确定的输出,不会因函数外部其他环境影响而改变输出结果。

reducer函数样例

例如,我有个reducer函数saveNotify,其实现了将payload中的notification信息更新到state中:

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的不变性。

[
    'umi-plugin-react',
      {
        dva: {
          immer: true
        },
      }
]
复制代码

这就是为什么我们可以直接操作传入的state,改变其值,因为此时传入的state并不是最原始的state,而是一个Proxy对象,当我们改变它的值,immer会自动和初始state做merge的操作。如下图所示:

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

转载于:https://juejin.im/post/5c96f4475188252d5a149644

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值