Testing React Apps
Facebook ではJestを使用して、Reactアプリケーションをテストします。
セットアップ #
Create React Appを使用したセットアップ #
Reactを使い始めたばかりなら、Create React Appの利用をお勧めします。 すぐに使えて Jestも同梱されています! セットアップに追加のステップは必要なく、次の章にそのまま進めます。
Create React Appを使わないセットアップ #
既存のアプリケーションがある場合は、いくつかのパッケージをインストールしてうまく機能するようにする必要があります。 babel-jest
パッケージと react
のbabel presetをテスト環境内のコードを変換するのに利用しています。 using babelも参照して下さい。
実行
npm install --save-dev jest babel-jest babel-preset-es2015 babel-preset-react react-test-renderer
package.json
は以下のようなものになっているはずです( <current-version>
はパッケージの実際の最新版のバージョンの数字になります)。 scriptsとJestの設定のエントリを追加して下さい:
// package.json "dependencies": { "react": "<current-version>", "react-dom": "<current-version>" }, "devDependencies": { "babel-jest": "<current-version>", "babel-preset-es2015": "<current-version>", "babel-preset-react": "<current-version>", "jest": "<current-version>", "react-test-renderer": "<current-version>" }, "scripts": { "test": "jest" }
// .babelrc { "presets": ["es2015", "react"] }
それでは次へ進みましょう!
スナップショットテスト #
ハイパーリンクをレンダリングするLinkコンポーネントのsnapshot testを作成しましょう:
// Link.react.js import React from 'react'; const STATUS = { HOVERED: 'hovered', NORMAL: 'normal', }; export default class Link extends React.Component { constructor(props) { super(props); this._onMouseEnter = this._onMouseEnter.bind(this); this._onMouseLeave = this._onMouseLeave.bind(this); this.state = { class: STATUS.NORMAL, }; } _onMouseEnter() { this.setState({class: STATUS.HOVERED}); } _onMouseLeave() { this.setState({class: STATUS.NORMAL}); } render() { return ( <a className={this.state.class} href={this.props.page || '#'} onMouseEnter={this._onMouseEnter} onMouseLeave={this._onMouseLeave}> {this.props.children} </a> ); } }
コンポーネントとのやり取りとレンダリングされた出力をキャプチャしてスナップショットファイルを作成するために、ReactのテストレンダラーとJestのスナップショット機能を利用しましょう:
// Link.react-test.js import React from 'react'; import Link from '../Link.react'; import renderer from 'react-test-renderer'; test('Link changes the class when hovered', () => { const component = renderer.create( <Link page="http://www.facebook.com">Facebook</Link> ); let tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback tree.props.onMouseEnter(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback tree.props.onMouseLeave(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); });
npm test
または jest
を実行すると、このようなファイルが出力されます:
// __tests__/__snapshots__/Link.react-test.js.snap exports[`Link changes the class when hovered 1`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `; exports[`Link changes the class when hovered 2`] = ` <a className="hovered" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `; exports[`Link changes the class when hovered 3`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]}> Facebook </a> `;
次回のテストでは、レンダリングされた出力は前に作成されたスナップショットと比較されます。 スナップショットは、コードの変更に沿ってコミットされるべきです。 スナップショットテストが失敗した場合、それが意図的な変更かどうかを点検する必要があります。 変更が予想されたものであればjest -u
コマンドでJestを実行して既存のスナップショットを上書きします。
この例のコードはexamples/snapshotから利用可能です。
DOM のテスト #
レンダリングされたコンポーネントをアサートし操作したいのなら、e Enzyme もしくは Reactの TestUtilsが利用できます。 この例ではEnzymeを使用します。
Enzymeを使用するには npm install --save-dev enzyme
を実行する必要があります。15.5.0より以前のバージョンのReactを使用している場合は react-addons-test-utils
もインストールする必要があります。
2 つのラベルを入れ替える単純なチェックボックスを実装してみましょう。
// CheckboxWithLabel.js import React from 'react'; export default class CheckboxWithLabel extends React.Component { constructor(props) { super(props); this.state = {isChecked: false}; // bind manually because React class components don't auto-bind // http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding this.onChange = this.onChange.bind(this); } onChange() { this.setState({isChecked: !this.state.isChecked}); } render() { return ( <label> <input type="checkbox" checked={this.state.isChecked} onChange={this.onChange} /> {this.state.isChecked ? this.props.labelOn : this.props.labelOff} </label> ); } }
この例ではEnxymeの shallow rendererを使用します。
// __tests__/CheckboxWithLabel-test.js import React from 'react'; import {shallow} from 'enzyme'; import CheckboxWithLabel from '../CheckboxWithLabel'; test('CheckboxWithLabel changes the text after click', () => { // Render a checkbox with label in the document const checkbox = shallow( <CheckboxWithLabel labelOn="On" labelOff="Off" /> ); expect(checkbox.text()).toEqual('Off'); checkbox.find('input').simulate('change'); expect(checkbox.text()).toEqual('On'); });
この例のコードはexamples/enzymeから利用可能です。
独自のコード変換処理 #
より高度な機能が必要な場合は、独自のコード変換処理を作成することもできます。 babel-jestを使う代わりに、ここではbabelを使う例を示します:
// custom-transformer.js 'use strict'; const babel = require('babel-core'); const jestPreset = require('babel-preset-jest'); module.exports = { process(src, filename) { if (babel.util.canCompile(filename)) { return babel.transform(src, { filename, presets: [jestPreset], retainLines: true, }).code; } return src; }, };
この例を動作させるには babel-core
とbabel-preset-jest
パッケージをインストールすることを忘れないで下さい。
これをJestと動作させるにはJestに次の設定を追加する必要があります:"transform": {"\\.js$": "path/to/custom-transformer.js"}
babelのサポート機能を使ってコードの変換処理を構築したい場合は、babel-jestを変換処理の作成に利用し、その中に独自の構成オプションを渡すことができます。
const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: ['my-custom-preset'], });