为了确保软件质量,保证函数逻辑的正确性,咱们通常会进行单元测试。本文主要讲述了在基于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的测试方法。固然,你们在平时写程序的过程当中,也要考虑到如何让测试更方便更简洁合理,而不是只为了实现功能而写代码。