30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

JestでReactのテストをする(5) テキストボックスやテキストエリアのテスト

クリックイベントのテスト方法は以前書いた。

JestでReactのテストをする(2) クリックイベントのテスト

今回は、テキストボックスやテキストエリアのチェンジイベントのテスト方法について書く。

テストの対象となるコードを準備する

まず、サンプルとして以下のコンポーネントを作成した。

// input.js
import React from 'react';

export default class InputField extends React.Component {
  constructor(props){
    super(props);
    this.state = {remainCount:10, value:''};
  };
  reflectInput(length, value){
    this.setState({remainCount:10-length, value});
  };
  render(){
    return(
      <div>
        <input type="text" onChange={e=>{
          this.reflectInput.bind(this)(e.target.value.length, e.target.value);
        }}></input><br />
        <span>{this.state.remainCount}</span><br />
        <button type="button" onClick={()=>{
          if(this.state.remainCount === 10 || this.state.remainCount < 0){ return; };
          this.props.clickFunc(this.state.value);
        }}>submit</button>
      </div>
    );
  };
};

これをレンダリングすると、以下のようになる。

f:id:numb_86:20170120204938p:plain:w160

文字を入力すると、数字が減っていく。

f:id:numb_86:20170120204948p:plain:w160

10文字まで入力可能で、それ以上入力された状態だと、submitボタンを押しても何も起こらない。
また、何も入力されていない状態でも、submitボタンによる関数の呼び出しは起こらない。

以下のような仕組み。

  • submitボタンで実行する関数を、this.props.clickFuncとして外から渡す
  • stateで、入力可能文字数と入力内容をremainCountvalueとして保持する
  • テキストボックスでチェンジイベントが発生する度、setState()remainCountvalueの値を更新する
  • submitボタンを押すとまず、remainCountの値をチェックして、問題があればその時点で処理を終了する
  • 問題がなければ、valueを引数として関数を実行する

なお、ボタン押下時の文字数のチェックはstateの値ではなく、その都度DOMから取得して行ったほうがいいと思うが、サンプルとしての分かりやすさを優先して、このような形にした。

テストを書く

この場合、以下の2つをチェックすればいいと思う。

  • onChangeを検知して正しくstateを更新しているかどうか
  • state.remainCountによるバリデーションが正しく機能しているか

以下がテストコード。

// __tests__/input.test.js
import React from 'react';
import {shallow} from 'enzyme';
import InputField from '../input.js';

describe('inputのテスト', ()=>{
  test('state', ()=>{
    const subject = shallow(<InputField />);
    let remainCount = subject.state().remainCount;
    expect(remainCount).toBe(10);
    subject.find('input').simulate('change', {target: {value: 'abc'}});
    remainCount = subject.state().remainCount;
    expect(remainCount).toBe(7);
  });
  test('click', ()=>{
    const mock = jest.fn();
    const subject = shallow(<InputField clickFunc={mock} />);
    subject.find('button').simulate('click');
    expect(mock).not.toHaveBeenCalled();
    subject.find('input').simulate('change', {target: {value: 'abc'}});
    subject.find('button').simulate('click');
    expect(mock).toHaveBeenCalledTimes(1);
    subject.find('input').simulate('change', {target: {value: 'qwerty'}});
    subject.find('button').simulate('click');
    expect(mock).toHaveBeenCalledWith('qwerty');
    expect(mock).toHaveBeenCalledTimes(2);
    subject.find('input').simulate('change', {target: {value: '01234567890'}});
    let remainCount = subject.state().remainCount;
    expect(remainCount).toBe(-1);
    subject.find('button').simulate('click');
    expect(mock).toHaveBeenCalledTimes(2);
  });
});

shallowtoHaveBeenCalledについては、冒頭のクリックイベントの記事を参照。

shallowの戻り値.find(対象となる要素).simulate(イベント名);

上記のように書くことで対象となる要素にイベントを起こせるのだが、これはchangeイベントも同様。
そして、simulate()の第二引数に{target: {value: 任意の文字列}}を渡すことで、対象の要素に任意の文字列をセットすることが出来る。

つまり以下は、subjectshallowレンダリングしたもの)のinput要素にabcという文字列をセットしたのと同じ意味を持つ。

subject.find('input').simulate('change', {target: {value: 'abc'}});

テストの結果、stateによるバリデーションは正しく機能しており、関数の呼び出しにも問題がないことを確認できた。

参考資料