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

881 阅读3分钟
原文链接: zhaozhiming.github.io

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

render逻辑的测试

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

class Footer extends Component {
  renderFooterButtons(completedCount, clearCompleted) {
    // 测试点1
    if (completedCount > 0) {
      return (
         clearCompleted() }>Clear completed
      );
    }
  }
  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 (
      
        {activeCount} item left
        
// 测试点2 {[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
  • style= onClick={ () => onShow(filter) }>{FILTER_TITLES[filter]} )} {this.renderFooterButtons(completedCount, clearCompleted)} ); } }

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

    • 在最上面有一个render button的方法,这个方法存在逻辑判断,如果completedCount大于0,则render一个button出来,否则不render button,这里是我们第一个测试点。测试代码如下,分别测试render和不render的情况:
    const props = {
      todos: [], // 空的数组
      actions: {
        clearCompleted: sinon.spy(), // mock方法
      },
      onShow: sinon.spy(), // mock方法
      filter: 'SHOW_ALL',
    };
    test('do not render button', t => {
      const wrapper = shallow();
      t.is(wrapper.find('button').length, 0);
    });
    test('render button correctly', t => {
      const wrapper = shallow();
      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和文本内容。
    test('render 3 li correctly', t => {
      const wrapper = shallow();
      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方法中有逻辑以外,在组件的事件中也会存在逻辑,要测试这部分代码,我们需要模拟触发组件的事件。请看下面这个组件:

    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 (
                  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的值,所以测试代码可以这样写:
    const props = {
      text: 'foo',
      placeholder: 'foo placeholder',
      editing: false,
      newTodo: false,
      onSave: sinon.spy(),
    };
    test('input change value correctly', t => {
      const wrapper = shallow();
      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方法,校验其调用次数。
    test('input blur correctly', t => {
      const wrapper = shallow();
      wrapper.find('input').simulate('blur', { target: { value: 'bar' } });
      t.is(props.onSave.callCount, 1);
    

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

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