使用AVA和Enzyme测试React组件(三)

Published on:

React组件的测试要点:React组件要怎么测试,有哪些需要注意的地方,今天我们通过一些例子来说明。

render逻辑的测试

React中存在逻辑的地方有一部分是在render方法中,React通过props或state的值可以render出不同的页面,所以我们可以通过设置不同的props值来测试是否能render出我们期望的页面。比如有下面这样的一个组件:

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

class Footer extends Component {
renderFooterButtons(completedCount, clearCompleted) {
// 测试点1
if (completedCount > 0) {
return (
<button className="clear-completed" onClick={ () => clearCompleted() }>Clear completed</button>
);
}
}

render() {
const { todos, actions, onShow } = this.props;
const { clearCompleted } = actions;
const activeCount = todos.reduce((count, todo) => todo.completed ? count : count + 1, 0);
const completedCount = todos.length - activeCount;
return (
<footer className="footer">
<span className="todo-count"><strong>{activeCount}</strong> item left</span>
<ul className="filters">
// 测试点2
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
<li key={filter}>
<a className={classnames({ selected: filter === this.props.filter })}
style={{ cursor: 'pointer' }}
onClick={ () => onShow(filter) }>{FILTER_TITLES[filter]}</a>
</li>
)}
</ul>
{this.renderFooterButtons(completedCount, clearCompleted)}
</footer>
);
}
}

可以看到这个组件其实是比较简单的,在一个footer标签里面有个span和一个ulul里面有一些li,最下面是个button

  • 在最上面有一个render button的方法,这个方法存在逻辑判断,如果completedCount大于0,则render一个button出来,否则不render button,这里是我们第一个测试点。测试代码如下,分别测试render和不render的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const props = {
todos: [], // 空的数组
actions: {
clearCompleted: sinon.spy(), // mock方法
},
onShow: sinon.spy(), // mock方法
filter: 'SHOW_ALL',
};

test('do not render button', t => {
const wrapper = shallow(<Footer {...props} />);
t.is(wrapper.find('button').length, 0);
});

test('render button correctly', t => {
const wrapper = shallow(<Footer {...props} />);
wrapper.setProps({ todos: [{ completed: true }] });
t.is(wrapper.find('button').length, 1);
});

要让completedCount不大于0,只要给个空的todos集合就可以了,如果要大于0的话,则需要在todos里面添加一个completed为true的对象,这里需要搞清楚completedCount的声明逻辑。

  • 第二个测试点是map方法里面的逻辑,因为是从一个有3个元素的数组里面做map,所以可以校验是否确实render出来3个li,以及其中某个li链接的class和文本内容。
1
2
3
4
5
6
7
test('render 3 li correctly', t => {
const wrapper = shallow(<Footer {...props} />);
wrapper.setProps({ todos: [{ completed: true }] });
t.is(wrapper.find('li').length, 3);
t.is(wrapper.find('a.selected').length, 1);
t.is(wrapper.find('a.selected').text(), 'All');
});

可以看到通过enzyme的text方法可以很方便地得到a标签的文本内容。

这个组件其实还继续做测试,比如span里面的render逻辑等,但这里就不详细举例了。

组件的事件逻辑

除了在render方法中有逻辑以外,在组件的事件中也会存在逻辑,要测试这部分代码,我们需要模拟触发组件的事件。请看下面这个组件:

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
class TodoInput extends Component {
constructor(props, context) {
super(props, context);
this.state = {
text: this.props.text || '',
};
}

handleChange(e) {
this.setState({ text: e.target.value });
}

handleBlur(e) {
if (!this.props.newTodo) {
this.props.onSave(e.target.value.trim());
}
}

render() {
return (
<input className={
classnames({
edit: this.props.editing,
'new-todo': this.props.newTodo,
})}
type="text"
placeholder={this.props.placeholder}
autoFocus="true"
value={this.state.text}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleChange.bind(this)}
/>
);
}
}

可以看到这个组件的render方法里面没有什么逻辑,只有一个input标签,但是在标签中存在了changeblur事件,组件的逻辑隐藏在对应的事件方法中。

  • 首先是对change事件的测试,我们可以看到handleChange方法其实是修改state里面text的值,所以测试代码可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
const props = {
text: 'foo',
placeholder: 'foo placeholder',
editing: false,
newTodo: false,
onSave: sinon.spy(),
};

test('input change value correctly', t => {
const wrapper = shallow(<TodoInput {...props} />);
wrapper.find('input').simulate('change', { target: { value: 'bar' } });
t.is(wrapper.state('text'), 'bar');
});

通过调用simulate方法对change事件进行模拟,然后调用state方法对组件的state进行校验。

  • 接着我们测试blur事件,handleBlur方法先做判断,如果为真则调用props中的onSave方法,我们可以用sinon来mock onSave方法,校验其调用次数。
1
2
3
4
test('input blur correctly', t => {
const wrapper = shallow(<TodoInput {...props} />);
wrapper.find('input').simulate('blur', { target: { value: 'bar' } });
t.is(props.onSave.callCount, 1);

模拟事件触发的方法差不多,都是传入事件名和所需的方法对象就可以了,这里校验onSave是否被调用了1次。

在写单元测试的时候,有一点要注意的是要避免过度测试,因为测试代码也是需要维护的,如果测试过多过细,那一旦生产代码有所改变,就可能会修改很多测试代码,需要开发人员需要在质量和开发效率上面做好均衡。

赞赏

Comments