Jest と ReactTestUtils で React Component のユニットテストを書く

  • 29
    Like
  • 0
    Comment
More than 1 year has passed since last update.

facebook製のテストフレームワークJestを使ってReact Component のユニットテストを書く方法について簡単にまとめました。

こちらに今回使ったサンプルコードをアップしましたので、合わせてご参照ください!

前提

  • jest-cli v0.9.2
  • babel-jest v9.0.3
  • react-addons-test-utils v0.14.7
  • react v0.14.7
  • babel で jsx, es6 をコンパイルする

Jest の主な特徴

設定

module のインストール

Jest でテストを実行するために必要な下記 module をインストールします。

$ npm install --save-dev jest-cli babel-jest react-addons-test-utils

package.json

  • scripts.test に jest を指定して、npm test コマンドでテストを実行できるようにします。
  • 必須 module を 自動的に mock 化させないために、jest.unmockedModulePathPatterns にreact, react-dom, react-addons-test-utilsを 指定します。
package.json
"scripts": {
  "test": "jest"
}

"jest": {
  "unmockedModulePathPatterns": [
    "<rootDir>/node_modules/react",
    "<rootDir>/node_modules/react-dom",
    "<rootDir>/node_modules/react-addons-test-utils"
  ]
}

テスト実行

以上の設定により、npm test で test が走るようになります!

$ npm test
> Jest-example@0.0.1 test [app_dir]/Jest-example
> jest

Using Jest CLI v0.9.2, jasmine2, babel-jest
No tests found for "". Regex used while searching: /.*/.

当然ですが、まだテストコードが存在しないため、テストは実行されません。
では、早速 Component のテストを書いてみましょう!

テストを書く

どこに?

テストコードは、__tests__ ディレクトリに配置していきます。
Jest が 自動的に __tests__ ディレクトリ内のテストを探して実行してくれるので、テストを一つのディレクトリにまとめても、各サブディレクトリに分割して配置してもOKです。

ファイル名は、[ModuleName]-test.js というルールで作成していきます。

何を?

今回テストするのは、下記のような単純なButton コンポーネントです。

js/components/Button.js
'user strict'
import React, { Component, PropTypes } from 'react'

class Button extends Component{
  render(){
    const{ onClick, labelText, disabled } = this.props;

    return(
      <input type="button" value={labelText} onClick={onClick.bind(this)} disabled={disabled}/>
    )
  }
}

Button.propTypes = {
  onClick: PropTypes.func.isRequired,
  labelText: PropTypes.string.isRequired,
  disabled: PropTypes.bool
}
Button.defaultProps = { disabled: false };

export default Button

仕様は下記の通りです。

  • property として、onClick(必須), labelText(必須), disabled(任意) を受け取る。
  • button が クリックされたら、onClick property を実行する。
  • labelText property を、buttonの value 属性にセットする。
  • disabled property を、button のdisabled 属性にセットする。
  • disabled の デフォルト値は false.

どうやって?

上記仕様をテストするコードを、下記のように記述しました。(解説は後述)

js/components/__tests__/Button-test.js
'use strict'

jest.unmock('../Button')

import React from 'react'
import ReactDOM from 'react-dom'
import TestUtils from 'react-addons-test-utils'
import Button from '../Button'

describe('Button', () => {
  let props ={
    labelText : "hoge",
    onClick : jest.fn()
  }

  function setup(){
    const instance = TestUtils.renderIntoDocument(
      <Button {...props} />
    )
    const buttonNode = ReactDOM.findDOMNode(instance)
    return {
      instance,
      buttonNode
    }
  }

  describe('click', () =>{
    it('calls onClick props', () => {
      const {buttonNode} = setup()

      TestUtils.Simulate.click(buttonNode)
      expect(props.onClick).toBeCalled()
    })
  })

  describe('value', () =>{
    it ('should be equal to label props', ()=>{
      props.labelText = "foo"
      const {buttonNode} = setup()

      expect(buttonNode.value).toEqual("foo")
    })
  })

  describe('disabled', () =>{
    it ('shoudl be false by default', ()=>{
      const {buttonNode} = setup()
      expect(buttonNode.disabled).toBeFalsy()
    })

    it ('should be equal to disabled props', ()=>{
      props.disabled = true
      const {buttonNode} = setup()

      expect(buttonNode.disabled).toBeTruthy()
    })
  })
})

重要なポイント

テスト対象 module の mock 化を回避する

3行目で、テスト対象module を unmockしています。

jest.unmock('../Button')

jest では、テストコード内の require の戻り値は自動的に mock object になるため、実際のmodule が必要な場合は、必ず unmock する必要があります。


テスト対象 Component を DOM に render する

setup 関数の中で、Button Component を DOM にrender しています。

const instance = TestUtils.renderIntoDocument(
  <Button {...props} />
)

render された DOM を取得する

今回のケースでは、Button Component は 一つの input 要素 を render しているだけなので、ReactDOM.findDOMNode を使って DOM を取得しています。

const buttonNode = ReactDOM.findDOMNode(instance)

ReactTestUtils には 他にもDOM を取得する function が定義されているので、ケースによって使い分けていきましょう。
https://facebook.github.io/react/docs/test-utils.html


event の シミュレート

ボタンクリック時の仕様をテストするために、ReactTestUtils.Simulate を使って、click インベントを emit しています。

describe('click', () =>{
  it('calls onClick props', () => {
    const {buttonNode} = setup()

    TestUtils.Simulate.click(buttonNode)
    expect(props.onClick).toBeCalled()
  })
})

React で 使える event は全てシミュレートできます。

再びテスト実行

$ npm test
> Jest-example@0.0.1 test [app_dir]/Jest-example
> jest

Using Jest CLI v0.9.2, jasmine2, babel-jest
 PASS  js/components/__tests__/Button-test.js (1.035s)
4 tests passed (4 total in 1 test suite, run time 1.814s)

テストが通りました!

使ってみて

  • 「自動的に依存モジュールが unmock される」仕様に慣れてしまえば、かなりシンプルにテストを記述できそう。=> とりあえずユニットテストは Jest に絞っていろいろ書いてみる。

  • どこまでComponentのテストを書くかは、まだ模索中。実践しながら落としどころを探っていく必要がある。ここまで書いといてなんだけど、今回の様な粒度でのComponentのテストは、必要ないのかもしれない。

  • flux では store のテストを書くのが再重要? => 調べて記事にする。

今後の課題

  • 非同期関数のテストを書く。
  • flux, redux のテストについてまとめる。