jest + enzmye + ts测试react踩坑记

如大家所知,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转换后,箭头函数被转换成了实例上的方法,而非原型上的方法。

解决办法有:

  1. overlayOnClick function改成在construtorbind形式,不要使用箭头函数。

  2. 采用下面方法

    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
  // component
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组件时,mountshallow 返回的包装的组件实际上不是它所构造的构造函数的实例。返回的是高阶组件包着传进去的子组件。同时相应的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
// ScrollNav component
// ...
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却是改变了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!