用mocha对react组件进行单元测试

unit testing a react component with mocha

本文关键字:单元测试 组件 react mocha      更新时间:2023-09-26

我正在研究Redux生态系统的TodoMVC示例。我已经完成了示例的工作代码,现在正在为应用程序的每个元素创建测试。

对于动作和减少器,测试非常简单,但是对于组件,编写测试被证明更具挑战性。

我的通用组件架构是这样的:

Home.js
      '-App.js
              '-TodoList.js
                          '-TodoItem.js
                                       '-TodoInput.js

为TodoInput.js编写单元测试相对简单:

TodoInput.js:

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

TodoInput-test.js:

const mockedTodo = {
  text: 'abc123',
  complete: false
};

it(`should update text from user input`, () => {
      const component = TestUtils.renderIntoDocument(
        <TodoInput
          text = {mockedTodo.text}
          editing = {false}
          onSave = {_.noop}
        />
      );
      const inputComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
      expect(React.findDOMNode(inputComponent).value).toBe(mockedTodo.text);
      TestUtils.Simulate.change(React.findDOMNode(inputComponent), {target: {value: "newValue"}});
      expect(React.findDOMNode(inputComponent).value).toBe("newValue");
      React.unmountComponentAtNode(React.findDOMNode(component));
});

但是对于TodoItem.js,测试有点棘手。

根据是否在项目上设置了editing标志进行渲染代码分支:

TodoItem.js:

import React, { Component, PropTypes } from 'react';
import TodoInput from './TodoInput';
import classnames from 'classnames';
export default class TodoItem extends Component {
  static propTypes = {
    todo: PropTypes.object.isRequired,
    editTodo: PropTypes.func.isRequired,
    markTodoAsComplete: PropTypes.func.isRequired,
    deleteTodo: PropTypes.func.isRequired
  }
  constructor(props, context) {
    super(props, context);
    this.state = {
      editing: false
    };
  }
  handleDoubleClick() {
    this.setState({ editing: true });
  }

  handleSave(id, text) {
    if (text.length === 0) {
      this.props.deleteTodo(id);
    } else {
      this.props.editTodo(id, text);
    }
    this.setState({ editing: false });
  }
  render() {
    const {todo, markTodoAsComplete, deleteTodo} = this.props;
    let element;
    if (this.state.editing) {
      element = (
        <TodoInput text={todo.text}
                       editing={this.state.editing}
                       onSave={(text) => this.handleSave(todo.id, text)} />
      );
    } else {
      element = (
        <div className='view'>
          <label onDoubleClick={this.handleDoubleClick.bind(this)}>
            {todo.text}
          </label>
          <input className='markComplete'
                 type='checkbox'
                 checked={todo.complete}
                 onChange={() => markTodoAsComplete(todo)} />
          <button className='destroy'
                  onClick={() => deleteTodo(todo)} />
        </div>
      );
    }
    return (
      <li className={classnames({
        completed: todo.complete,
        editing: this.state.editing
      })}>
        {element}
      </li>
    )
  }
}

我对如何编写一个测试有点困惑,例如,验证双击组件是否成功地将状态设置为editing: true

通常,我把我的测试分为两部分,"渲染"answers"事件",也就是说,对于TodoItem-test.js:

import React, { addons } from 'react/addons';
import _ from 'lodash';
import expect from 'expect';
const { TestUtils } = addons;
import TodoItem from '../TodoItem';
describe('TodoItem', () => {
  const mockedTodo = {
    text: 'abc123',
    complete: false
  };
describe('rendering', () => {
    let component;
    before(() => {
      component = TestUtils.renderIntoDocument(
        <TodoItem
          todo={mockedTodo}
          editTodo={_.noop}
          markTodoAsComplete={_.noop}
          deleteTodo={_.noop}
        />
      );
    });
    afterEach(() => {
      React.unmountComponentAtNode(React.findDOMNode(component));
    });
    it('should render the element', () => {
      const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
      expect(liComponent).toExist();
    });
    it('should render text in label', () => {
      const labelComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'label');
      expect(labelComponent).toExist();
      expect(React.findDOMNode(labelComponent).textContent).toEqual('abc123');
    });
  });
 describe('events', () => {
  ...
});

,但在本例中,我想看看双击组件是否会导致以下结果:

  1. 组件状态现在应该有一个与之相关的editing标志
  2. element应该已经改变了,TodoItem.js现在应该渲染一个<TodoInput/>组件。

根据预期行为构建测试的最有效方法是什么?我想我应该做两件事:

首先,测试一下双击组件是否添加了预期的"editing: true"标志。我不确定如何做到这一点。如果我设置一个测试,如下所示:

describe('events', () => {
    let component;
    let deleteTodoCallback = sinon.stub();
    beforeEach(() => {
      component = TestUtils.renderIntoDocument(
        <TodoItem
          todo={mockedTodo}
          editTodo={_.noop}
          markTodoAsComplete={_.noop}
          deleteTodo={deleteTodoCallback}
        />
      );
    });
    afterEach(() => {
      React.unmountComponentAtNode(React.findDOMNode(component));
    });
    it(`should change the editing state to be true if a user double-clicks
          on the todo`, () => {
        const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
        // expect the editing flag to be false
        TestUtils.Simulate.doubleClick(React.findDOMNode(liComponent));
        // expect the editing flag to be true
    });
  });

我如何进行测试以确保编辑标志已设置? liComponent.props.editing返回undefined.

第二,有一个context("if the component is editing mode")测试,以确保以下内容已正确呈现:

  <li className={classnames({
    completed: todo.complete,
    editing: this.state.editing
  })}>
      <TodoInput text={todo.text}
                   editing={this.state.editing}
                   onSave={(text) => this.handleSave(todo.id, text)} />
  </li>

我也不确定如何严格地进行测试。

liComponent.props是未定义的,因为liComponent是一个DOM元素,而不是react组件。这是因为你是用findRenderedDOMComponentWithTag来取它的。你实际上已经可以访问你要测试的React组件了。

it('should render the element', () => {
    const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
    // `component` is from your `before` function
    expect(component.state.editing).toBe(false);
    // Make sure you're simulating on a DOM element
    TestUtils.Simulate.doubleClick(liComponent);
    expect(component.state.editing).toBe(true);
});

你可以使用scryRenderedComponentsWithType来检查TodoInput是否被渲染