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 の主な特徴
-
テストコード内で require した module は、自動的に mock 化される(後述)。https://facebook.github.io/jest/docs/automatic-mocking.html
-
デフォルトで jasmine 2を利用している。
設定
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を 指定します。
"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 コンポーネントです。
'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.
どうやって?
上記仕様をテストするコードを、下記のように記述しました。(解説は後述)
'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 のテストについてまとめる。