ここまでのテストではgetJSONが完了するまでの間に表示される「確認中です...」が実際に行われているかテストしていませんでした。
もちろん、この内容もテストするべきでしょう。しかし、サーバサイドから非同期的に値を取得して更新を実施するため、画面上のメッセージが更新されるタイミングはサーバの応答時間次第、画面上のメッセージの更新内容は値次第となってしまいます。
テストのためのサーバを立てるのも大変ですし、Webブラウザ側のテストのためにサーバに手を入れるのは美しくありません。ここでSinon.JSの出番がやってきます。
Sinon.JSは冒頭で紹介したとおり、スタブ、モック、スパイなどの機能を提供します。上記の問題を解決するために、Sinon.JSの「Fake server」の機能を利用します。
Fake ServerはAjaxのリクエストに対して実際の通信を行わずダミーのレスポンスを戻すことができます。ここでは、APIへの通信をフックして通信の間に表示される「確認中」が出ることと、{status: ok}を応答として受け取ったときに利用可能ですと表示されることをテストします。
利用の前に「/features/js/」にSinon.JSをダウンロードしておきます。
mkdir features/js cd features/js wget http://sinonjs.org/releases/sinon-1.6.0.js
次に、sample.featureとsteps.rbに以下を追記します。
シナリオ: 利用可否の確認中メッセージが表示された後利用可のメッセージが表示されること。 前提 "http://localhost:8000/"を表示 かつ Sinon.JSを利用する かつ fakeserverを設定する もし "ねこ"を入力 かつ 確認ボタンを押下 ならば メッセージ"確認中"が表示される もし ユーザー名が利用可の応答を受け取る ならば メッセージ"利用可能です"が表示される
前提 'Sinon.JSを利用する' do f = File.open('features/js/sinon-1.6.0.js', "r") sinon = f.read; page.execute_script sinon f.close end 前提 'fakeserverを設定する' do page.execute_script <<-JS server = sinon.fakeServer.create(); JS end もし 'ユーザー名が利用可の応答を受け取る' do # 最後に受けたRequestに対して応答する page.execute_script <<-JS var idx = server.requests.length - 1; server.requests[idx].respond(200, {"Content-Type": "application/json"}, '{"status":"ok"}'); JS end
Capybaraを通してSinon.JSのロード、Fake Serverの実行などを行います。JavaScriptを実行するときはCapybaraの「execute_script」「evaluate_script」を使います。
「sinon.fakeServer.create()」を設定した時点で、Ajaxの通信は実際には行われなくなり、Sinon.JSからのダミーのレスポンスを受け取るようになります。ダミーのレスポンスを返す部分は、ステータスコードとヘッダとボディの3つを設定します。
次に、Sinon.JSを用いてgetJSONの引数が正しく設定されているか確認してみます。引数のチェックや呼ばれた回数などをチェックするにはspyを使います。
さらに、features/sample.featureにシナリオを追記します。
シナリオ: getJSONの引数チェック 前提 "http://localhost:8000/"を表示 かつ Sinon.JSを利用する かつ getJSONにspyを設定する もし "いぬ"を入力 かつ 確認ボタンを押下 ならば 引数"いぬ"でgetJSON処理を実行する かつ getJSON処理が1回実行されている もし "ねこ"を入力 かつ 確認ボタンを押下 ならば 引数"ねこ"でgetJSON処理を実行する かつ getJSON処理が2回実行されている
features/step_definitions/steps.rbにも以下の内容を追記します。
前提 'getJSONにspyを設定する' do page.execute_script <<-JS var spy = sinon.spy($, "getJSON"); JS end ならば /引数\"(.+)\"でgetJSON処理を実行する/ do |name| js = "spy.calledWith('./status', {'name': \'#{name}\'})" ok = page.evaluate_script js unless ok raise "unexpected param" end end ならば /getJSON処理が(\d+)回実行されている/ do |expect| count = page.evaluate_script <<-JS spy.callCount; JS if count.to_i != expect.to_i raise "unexpected callcount #{count} != #{expect}" end end
$オブジェクトのgetJSONメソッドに対してspyを設定し、spyの「CalledWith」「callCount」を使って想定した引数で呼び出されているかをテストしています。CapybaraによってJavaScriptの実行結果をRuby側で取得して、その結果を元に想定値と現実の比較を行っています。
ここまでの修正で全体のテスト結果は、こうなりました。
というわけで、本記事では非常に簡単なWebアプリケーションを対象にCapybara-WebkitとSinon.JSを組み合わせ、Cucumberで記載したテストをヘッドレスに実行しました。
通常、「Webブラウザでページを開いて」「何かを入力して」「ボタンを押して」と実際に動作確認をしなければならないと開発者に思われていた部分について、コマンドラインからのテスト実行であっという間にテストできるため、テストを書く手間さえ惜しまなければ非常に楽ができるはずです。
今回はCapybara-Webkitを記事では取り上げましたが、同様のものとして「Poltergeist」(Capybara向けPhantomJSドライバ)があります。
CucumberはJavaScriptのテストという観点からは少し逸脱しますがWebページをテストする際にCapybara/DSLと組み合わせたことでユーザーの操作を自動化でき、ユーザー操作に付随して動作するJavaScriptの動作確認も可能である点は非常に省力化が期待できると思います。
また、テスト対象がWebアプリケーションであれば、そのアプリケーションがPerl/PHP/PythonなどのRuby以外の言語で書かれていてもテストが可能なのは非常に大きいと思います。
Sinon.JSは今回の記事内では少々トリッキーな利用方法をしていましたが、Fake Serverとspyの強力な機能はお伝えできたと思います。
Sinon.JSは記事内で利用しなかった強力な機能もあるので、これまでの連載で取り上げられている単体テスト向けのフレームワークであるQUnitやJasmine、JSTestDriverなどとの連携させることでテストの適用範囲が広げられるでしょう。
これまでは「JavaScriptだからテストコードは書かない」という世界もありましたが、本連載で取り上げたようにJavaScriptをテストするためのフレームワークが十分に実用可能になっています。
これからはJavaScriptのテストもガンガン書いて快適なJavaScriptライフを送りましょう!
Copyright © ITmedia, Inc. All Rights Reserved.