如大家所知,react官方提供了react-dom/test-utils
和rreact-test-renderer
来对react进行单元测试,而enzyme就是对他们的封装,更加便于开发者的使用。
那么在enzyme中shallow vs render vs mount 这3者有什么区别呢?
- shallow只会渲染一层元素,对于子组件是不渲染的。
- 如果需要测试组件生命周期,使用mount。
- 如果想测试渲染多层组件,但并不关心生命周期,使用render。
建议是尽量使用shallow, 性能会好点。
坑一:
如果在component中用箭头函数定义function,也采取jest.spyOn(Comp.prototype, 'overlayOnClick')
;
1 2 3
| overlayOnClick = () => { };
|
报错在原型上找不到该方法
原因是经过ts转换后,箭头函数被转换成了实例上的方法,而非原型上的方法。
解决办法有:
将overlayOnClick function
改成在construtor
中bind
形式,不要使用箭头函数。
采用下面方法
1 2 3 4 5
| const instance = wrapper.instance() as any; // 不加这行会报错error TS2345 overlayOnClick 在wrapper.instance上找不到相应的类型 const spy = jest.spyOn(instance, 'overlayOnClick'); instance.forceUpdate(); //由于在spy之前已经mount,所以需forceUpdate wrapper.find('.overlay').simulate('click', {target: instance.overlay}); expect(spy).toHaveBeenCalledTimes(1);
|
坑二:
需要测试这样一个case, 在document.body
上面监听按键事件,一开始以为不过就是simulate中click事件换成相应的key事件,然而结果并非如此,测试失败,simulate并没有触发。
1 2 3 4 5 6 7
| componentDidMount() { document.body.addEventListener('keyup', this.onEsc, false); } componentWillUnmount() { document.body.removeEventListener('keyup', this.onEsc, false); }
|
1 2 3 4 5 6
| // test spec const instance = wrapper.instance() as any; const spy = jest.spyOn(instance, 'onEsc'); instance.forceUpdate(); wrapper.find('.rc-dialog').simulate('keyup', {which: 27}); expect(spy).toHaveBeenCalledTimes(1);
|
原来是由于document.body.addEventListener已经并非react中的事件了,而enzyme其实是对ReactTestUtils.Simulate的封装,所以自然不能处理非react的合成事件。
解决方法:
1 2 3 4 5 6 7 8 9 10 11
| const map = { keyup: null }; document.body.addEventListener = jest.fn((event, cb) => { map[event] = cb; });
const wrapper = shallow( <COMPONENT /> ); map.keyup({which: 27});
|
坑三:
测试Hoc组件时,mount
或shallow
返回的包装的组件实际上不是它所构造的构造函数的实例。返回的是高阶组件包着传进去的子组件。同时相应的prop也render在子组件上面,显示如下:
1 2 3 4 5 6 7
| <HWC> <WrapperComp Loaded={false}> <h1> wrpper comp </h1> </WrapperComp> </HWC>
|
1 2 3 4 5 6 7 8 9 10 11
| export default function HWC(WrapComponent) { return class HC extends React.Component { state = { loaded: false }; render() { return <WrapComponent {...this.state} {...this.props} />; } }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // test spec class WrapperComp extends React.Component { render() { return <h1>wrpper comp</h1>; } }
const WrapperWithGt = HWC(WrapperComp); describe('test with hoc', () => { beforeAll(() => { wrapper = mount( <WrapperWithGt /> ); }); it('should wrapper with loaded prop', () => { expect(wrapper.find('WrapperComp').props()).toHaveProperty('loaded'); // 这边如果换成h1元素,是无论如何也找不到loaded属性的 }); })
|
坑四:
在component中有这样一行语句,location.reload();
报错如下,
由于jsdom不支持导航,因此window.location.href或类似就会报错。可以对方法进行mock,解决如下:
window['location']['reload'] = jest.fn();
坑五:
当需要对window上的scroll事件进行监听测试时:
1 2 3 4 5 6 7 8 9 10
|
componentDidMount() { this.debounceFunc = debounce(this.getScrollAnchor, 80); window.addEventListener('scroll', this.debounceFunc, false); } getScrollAnchor = () => { };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| // test spec const list = [ { anchor: 'mockAnchor', title: 'mock title1', onChangeAnchor: jest.fn() }, { anchor: 'mockAnchor2', title: 'mock title2', onChangeAnchor: jest.fn() } ]; describe('scrollNav window scroll event', () => { let wrapper = null; const map = { scroll: null };
beforeAll(() => { const ele1 = document.createElement('div'); const ele2 = document.createElement('div'); ele1.setAttribute('id', 'mockAnchor'); ele2.setAttribute('id', 'mockAnchor2'); document.body.appendChild(ele1); document.body.appendChild(ele2); window.addEventListener = jest.fn((event, cb) => { map[event] = cb; });
wrapper = shallow( <ScrollNav lists={list} /> ); });
it('should trigger scroll event', () => { const instance = wrapper.instance() as any; const spy = jest.spyOn(instance, 'getScrollAnchor'); instance.forceUpdate(); map.scroll(); jest.advanceTimersByTime(80); expect(spy).toHaveBeenCalled(); }); })
|
总是报错:
没明白为何wrapper上的instance没被spy到? 但是相应的state却是改变了。