3

私は、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

通常、テストは「レンダリング」と「イベント」の 2 つの部分に分けられます。つまり、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/>

この予想される動作に対してテストを構成する最も効率的な方法は何ですか? 私は次の2つのことを行うべきだと考えています。

最初に、コンポーネントをダブルクリックして予期した"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>

また、これを厳密にテストする方法もわかりません。

4

1 に答える 1