Tips 2:React.addons.TestUtils+Jestでユニットテストを作成する
ここでテストについても触れておきましょう。
React.jsは、コンポーネントのテストに特化したユーティリティをアドオンとして提供しています(公式ドキュメントを参照)。テストフレームワークには何を使ってもよいのですが、公式ドキュメントでFacebook製の「Jest」が紹介されているので、簡単に紹介します。
Jestとは
Jestは、BDD(振る舞い駆動開発)テストフレームワークとして人気の「Jasmine」を拡張したライブラリで、依存するすべてのモジュールを自動的にモックするのが大きな特徴です。これによって、確実にテスト対象だけをテストすることができます。
本稿執筆時点(2015年3月2日)で、Jestは最新バージョンのNode.js(v0.12.0)で動作しません。Jestの代わりにMochaを使う場合は、後述のTips 3を参考にしてください。
Jestのインストールと準備
まず、npmを使ってJestをインストールします。
$ npm install jest-cli --save-dev
次に、Reactのアプリケーションでテストを行うために、次の準備を行います。
-
react
モジュールを自動モック化の対象から外す(必須) - プリプロセッサによって、事前にJSXをJavaScriptにコンパイルする(JSX使用時は必須)
-
テストを配置するディレクトリ名を「test」に変更する(任意。デフォルトは
__tests__
) -
npm test
コマンドでテストを実行できるようにscripts
に追記する(任意)
各設定はpackage.json内で管理します。"scriptPreprocessor"
で指定されているpre-test.jsが、JSXをJavaScriptにコンパイルするプリプロセッサです。
"scripts": { "test": "jest", ... }, ... "jest": { "testDirectoryName": "test", "scriptPreprocessor": "<rootDir>/pre-test.js", "unmockedModulePathPatterns": [ "react", "object-assign" ] }
var ReactTools = require('react-tools'); module.exports = { process: function(src) { return ReactTools.transform(src); } };
Windows環境にJestを導入する場合、いくつか乗り越えるべき障害があるかもしれません。筆者はこちらを参考にしました。
また、モック化によって特定のモジュールが壊れるバグが報告されています。例ではobject-assign
をモック対象から外して回避しています。
テストを書く
せっかくのBDDライブラリですので、テスト駆動で書くためにTODOアプリに次の仕様を追加します。
-
TodoStorage.create
のコールバック関数がエラーオブジェクトを返した場合には、入力欄を空欄にしない
テストの都合上、<TodoForm>
コンポーネントをtodo-form.jsに切り出しました。続いて、todo-form-test.jsに次のようにテストを記述します[7]。
// テスト対象のモジュールをモック対象から外す jest.dontMock('../src/todo-form.js'); describe('todo-form', function() { it('should keep input value when submission fails', function() { var React = require('react/addons'); var TodoForm = require('../src/todo-form.js'); var TestUtils = React.addons.TestUtils; var form = TestUtils.renderIntoDocument(<TodoForm/>); // 入力が'ok'でない場合にエラーを返すように、TodoStorageのモックオブジェクトを作成 var TodoStorage = require('../src/storage.js'); TodoStorage.create.mockImplementation(function(name, callback) { callback(name === 'ok' ? null : 'error'); }); var input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'input')[0]; var button = TestUtils.scryRenderedDOMComponentsWithTag(form, 'input')[1]; // 1: inputが'ok'の状態でsubmitすると、inputが初期化される TestUtils.Simulate.change(input, { target: { value: 'ok' }}); TestUtils.Simulate.submit(button); expect(TodoStorage.create).toBeCalled(); expect(input.getDOMNode().value).toBe(''); // 2: inputが'ng'の状態でsubmitすると、inputは初期化されずに残る TestUtils.Simulate.change(input, { target: { value: 'ng' }}); TestUtils.Simulate.submit(button); expect(TodoStorage.create).toBeCalled(); expect(input.getDOMNode().value).toBe('ng'); }); });
コードからは、React.addons.TestUtils
によって、DOMのイベントをシミュレートできていることが分かると思います。
[7] アサーションの書き方はJasmineのドキュメントを参照してください。
テストを通す
npm test
コマンドでテストが失敗しているのを確認したら、テストが通るように実装を追加します。
handleSubmit: function(e) { e.preventDefault(); var name = this.state.name.trim(); TodoStorage.create(name, function(error) { + if(!error) { this.setState({ name: '' }); + } }.bind(this)); },