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));
},
