Tips 3:React.addons.TestUtils+Mochaでユニットテストを作成する
Jestの代わりに「Mocha」を使う場合は、こちらの説明を参考にしてください。同じことを行うため、Tips 2と説明が重複しますがご容赦ください。
Mochaのインストールと準備
Mochaはテストライブラリとアサーションライブラリが独立していることが特徴のフレームワークで、自分の好きな組み合わせでテストを行うことができます。また、ブラウザ上のテストだけでなく、コマンドからNode.jsモジュールをテストすることもできます[8]。
ここでは、アサーションライブラリとして「should」、モックライブラリとして「Sinon」を使うことにします。
それから、通常Node.jsではブラウザテストができないため、DOMをシミュレートするために「jsdom」を使います。また、require(...)
をモックするために「proxyquire」を使います。
さらに、MochaではJSXを直接読むことができないため、事前にreact-tools(jsx
コマンド)を使ってコンパイルします。
まず、上記で挙げた必要なものをすべてインストールします。
$ npm install mocha should sinon jsdom react-tools proxyquire --save-dev
次に、npmから実行できるように、package.jsonの"scripts"
に記述します。
"scripts": { "pretest": "jsx src/ compiled-js/src/ & jsx test/ compiled-js/test/", "test": "mocha compiled-js/test --harmony", ... },
コマンド名に接頭辞pre
、post
を付けたコマンド(例えば"test"
に対して"pretest"
、"posttest"
)は、そのコマンドの実行前、実行後にそれぞれ実行されます。
[8] Mochaの詳しい使い方については、CodeZineの過去記事に説明があります、こちらを参照してください。
テストを書く
せっかくのBDDライブラリですので、テスト駆動で書くために次の仕様を追加します。
-
TodoStorage.create
のコールバック関数がエラーオブジェクトを返した場合には、入力欄を空欄にしない
テストの都合上、<TodoForm>
コンポーネントをtodo-form.jsに切り出しました。続いて、todo-form-test.jsに次のようにテストを記述します。
// ブラウザのグローバル変数をNode.js上で使うための準備 var jsdom = require('jsdom').jsdom; global.document = jsdom('<html><body></body></html>'); global.window = document.defaultView; global.navigator = window.navigator; var should = require('should'); var sinon = require('sinon'); var proxyquire = require('proxyquire'); describe('todo-form', function() { it('should keep input value when submission failed', function() { var React = require('react/addons'); var TestUtils = React.addons.TestUtils; // 入力が'ok'でない場合にエラーを返すように、TodoStorageのモックオブジェクトを作成 var TodoStorage = { create: function(name, callback) { callback(name === 'ok' ? null : 'error'); } }; TodoStorageSpy = sinon.spy(TodoStorage, 'create'); var TodoForm = proxyquire('../src/todo-form.js', { './storage.js': TodoStorage }); var form = TestUtils.renderIntoDocument( <TodoForm/> ); 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); TodoStorageSpy.withArgs('ok').callCount.should.equal(1); input.getDOMNode().value.should.equal(''); // 2: inputが'ng'の状態でsubmitすると、inputは初期化されずに残る TestUtils.Simulate.change(input, { target: { value: 'ng' }}); TestUtils.Simulate.submit(button); TodoStorageSpy.withArgs('ng').callCount.should.equal(1); input.getDOMNode().value.should.equal('ng'); }); });
このコードからも、React.addons.TestUtils
によって、DOMのイベントをシミュレートできていることが分かると思います。
テストを通す
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)); },
まとめ
以上、全2回にわたってReact.jsでのアプリケーション開発について駆け足で紹介してきました。ここで紹介した以外にも、アニメーションやサーバサイドレンダリングなど、高度なトピックはまだまだあります。必要に応じてキャッチアップするとよいでしょう。
今回はシングルページアプリケーションを丸ごとReact.jsで構築しましたが、実際には、最初から新規プロダクトの構築に着手する機会は多くないかもしれません。React.jsは他のフレームワークと比べて柔軟にライブラリを組み合わせて使えるように設計されているため、途中から導入することも可能でしょう(弊社でもいくつかのプロジェクトで一部に採用しています)。
最近になり、日本語でのReact.js紹介記事が増えており、導入の敷居が下がっています。この機会に選択肢の1つとして検討されてみてはいかがでしょうか。