简介
公司的前端项目的技术栈为 react + ts + Rxjs。react就不多说了,大型项目一般都会采用react,单向数据流比双向绑定渲染来的更加准确和内存的更少消耗(diff)。ts的强类型保证了数据的精准,减少bug。Rxjs则会更好的获取异步数据,不受其他元素影响。技术的革新带来的是代码和效率的质变,同时也会对一部分技术带来冲击。比如说老牌单元测试框架jest。下面我就描述一下在项目中遇到的jest测试难题和解决过程,后面还有可能遇到新的会再更新到博文中
1. 对axios的mock
相信用jest写react单元测试的同学少不了要测试带有axios回调数据的组件。平时测试过程也较简单,mock一下axios即可。类似代码如下;
jest.mock('axios');
...
axios.get.mockResolvedValue(resp); /// resp为mock后的数据,
但是如果采用ts了会怎样??
what???ts的强类型导致代码的报错。于是开始在网上搜寻到以下解决答案: 1.将axios.get 别名。虽然不会报错,但是any类型很明显不是我们想要的,违背了ts的初始点
(axios.get as any).mockResolvedValue(resp);
2.利用jest.mock()的回调函数来满足。这里确实能够做到一定程度的mock,但是如果使用了axios的拦截器的话,那么就准备重写一个axios吧。这种方式不推荐。
jest.mock('axios',() =>{
get:() =>{} // 这里定义成想要的mock形态
})
我们无非就是要mock组件内的方法,使组件内的方法传出我们要模拟的值来做测试。所以我们可以使用jest.spyOn.顾名思义就是间谍我们的方法。
import axios from 'axios';
...
jest.spyOn(axios,'get').mockResolvedValue(resp);
ts无报错,又能很好的mock值,一举多得。如果我们自己写了服务文件,那么可以这么弄
import * as server from './Server';
...
jest.spyOn(server,'getData').mockResolvedValue(resp);
2.在Rxjs中断言
Rxjs是一个操作异步的库,里面有很多操作符,如map、pipe...操作符中我们不要用来断言,我们一般已经将结果mock掉了,所以直接在订阅subscribe中断言即可。但是我们对Rxjs的方法进行mock的时候还是需要注意的
import {of} from 'rxjs';
import * as server from './Server';
...
jest.spyOn(server,'getData').mockResolvedValue(of(resp));//这里需要用of来发送mock的数据
同时记得在订阅里的结尾,写好 done()表示结束。
3.受到withRouter影响
组件读取路由的参数,方法有很多,最好的莫过于使用‘react-router-dom’的withRouter这个高阶组件。但是这又会造成一个很大的问题,比如我们按照平时方式使用enzyme来mount组件
import App from './App';//这里App组件使用了withRouter
...
const wapper = mount(<App/>);
然而命令行提醒我们
这是我们就需要使用Router组件包裹一下
import App from './App';//这里App组件使用了withRouter
import {MemoryRouter} from 'react-router-dom';
...
const wapper = mount(<MemoryRouter><App/></MemoryRouter>);
此时感觉一切顺利,该怎么测试怎么测试。可是,如果我们的App组件中用到了componentWillReceiveProps这个生命周期的时候我们会发现,使用enzyme的setProps()方法不能将新的props注入到App的componentWillReceiveProps生命周期中。因为我们用setProps将参数注入到了MemoryRouter中了。于是我们可以封装一个新的class来进行测试
import App from './App';//这里App组件使用了withRouter
import {MemoryRouter} from 'react-router-dom';
class testApp extends React.Component<Iprops>{ // 这里的Iprops类型就是App的Props类型
render(){
return(
<MemoryRouter><App {...this.props}/></MemoryRouter>
)
}
}
...
const wapper = mount(<testApp />);
这里就可以看上去比较完美的解决componentWillReceiveProps这里的传参问题了。 可是新问题出来了,该怎么获取App组件内的state呢?或者说内部props? 我们重新看一下官网描述的mount()方法;
后面还可以带一个option配置。提供上下文配置,childContextTypes。于是我们找到router 的childContext相关类型,如下:
于是我们可以稍微整理一下option: (这里要感谢朱德龙)
const option ={
childContextTypes: {
router: () => void 0, //包裹的router是函数类型
},
context: { // 上下文相关配置
router: {
history: createMemoryHistory(),
route: {
location: {
hash: '',
pathname: '',
search: '',
state: '',
},
match: { params: {}, isExact: false, path: '', url: '' },
}
}
}
}
...
const wapper = mount(<App/>,option);
现在,就可以各种操作App组件啦。得到state也好,更改props也好,按照enzyme上的文档操作即可。
原文链接:tech.gtxlab.com/jest-proble…
作者简介: 张栓,人和未来大数据前端工程师,专注于html/css/js的学习与开发。