53
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-27

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
'use 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 のテストについてまとめる。
53
49
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
49

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?