Edited at

AngularJSのTutorialのstep-5よりAjaxの書き方・テストの仕方

More than 5 years have passed since last update.

AngulaJSのTutorialstep-5を実施したレポートです。

個人的に最もAngularに魅かれた仕組みを説明してくれているTutorialで、

なんといってもポイントはController側もテスト側もDI経由のサービスを利用した実装です。

Angularは、エラーハンドリングはHTTPレスポンスコードで見ていたりして、サーバ側のIF設計がRESTful実装されていることがある程度、前提にしています。

そこで、このRESTfulについても重要なので、今回は簡単に補足します。


step-5

step-5では、


  1. ファイルシステム上のJsonファイルからデータを取得し、

  2. スマホリストを検索機能付きで表示する

上記の仕組みを実装したサンプルコードを元に課題を遂行します。


データ

このTutorialでは、データは静的なjsonファイルで定義されています。

このデータを使ってstep-4以前で出力していたスマホリストを置き換えます。

(サンプルとして静的なデータですが、実際のアプリでは、DBに格納されたデータをサーバから受け取ることをイメージしてください)


Controller

Ajax部分の実装を考えたとき、AngularのData Bindingの仕組みが更に生きてきます。

以下、チュートリアルのコードのController部分を抜粋。

phonecatApp.controller('PhoneListCtrl', function ($scope, $http)  {

Controllerの宣言部分で、$httpというサービスが出てきます。

Angularでは、組み込みのServiceの名前には「$」が頭につくようになっています。

そして、組み込みのServiceは使いたいときに差し込むことが出来るようです。

この$httpが、AngularのRemote アクセス用のServiceです。jQuery.ajax に相当するものですね。

$http単体で呼び出したいときは少し制限がありますが($httpの「Calling $http from outside AngularJS」参照)、Controllerで呼び出すに限ってはサンプルの通り引数に加えれば利用できるようです。

詳しく図解してくれていますが、JavaScriptって引数の解析が出来るんですね。

そして、$httpには、レスポンス制御のためにsuccessとerrorの2種類のメソッドが用意されており、以下のとおりデモではレスポンスコードが2xx系で呼ばれるsuccessだけを利用しています。

  $http.get('phones/phones.json').success(function(data) {

$scope.phones = data;
});

これが、巷で言うところのAjaxの仕組みを担ってくれる存在で、APIドキュメントの3行目にUnitTestsに関する情報が記載されています。一般的にテストに対する説明は割と後述になる印象があるんですが、テスト面を重視する私としては素晴らしく感じます。

データ部分がJsonで定義されていた場合、AngularのDIで利用するサービス$httpは、

受け取ったレスポンスデータをそのままテンプレート側で利用できるデータ書式にして渡してくれます。

(サーバから返されてきたデータのprefixが形式にマッチしていない場合はprefix部分を取りのいてくれるらしい)

以上の結果サンプルのように、$http.success で受け取ったデータを


$scopeのModelに突っ込むだけで、Template側のデータリスト出力がなされます。


テスト

Angularの好きなところをようやく説明できる所まで来ました。$httpBackendというMockを利用して行うテストの説明です。

Ajaxに関わるテストを書く場合、テスト用のサーバ側Controllerを用意してAjaxの接続先をそちらに向けるかもしくはProxyを立ててテスト用のデータを利用するように環境を整えるか。などを自前で組んでいるときには考えなければならないです。

(jQueryだと確実にそうなるはずです。プラグインか何か使っている場合はわかりませんが)

それに対して、Angularでは、$httpBackendというMockを使うことでサーバ側や環境など一切触れず、本stepのControllerの単体テストを書けます。

下記のように「_」アンダーバーを使ってパラメータを書いているのは、$httpBackendサービスを利用できるようにするためのおまじないのようです。詳しくはinjectを参照してください。

    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {

$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);

scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));

expectGetは、「'phones/phones.json'へGETメソッドで接続をしようとした場合に限り、respondの結果を返す」というルールを作成しており、テストしたい操作に合わせたデータを(例えば、名前が空文字だったら''にするとか)返すようにしてしまえばいいわけです。

上記で、テスト実行されるまで($httpBackend.flush()が呼び出されるまで)データを保持して待ち構えています。

そして、定義した仮のレスポンスを元に、期待する処理がControllerで行われたかどうかを下記のようにチェックするのです。

it('should create "phones" model with 2 phones fetched from xhr', function() {

expect(scope.phones).toBeUndefined();
$httpBackend.flush();

expect(scope.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});


  1. 一つ目のexpectでは、scope内にまだphonesモデルが作成されていないことをチェックする

  2. $httBackend.flush()で、用意していたMockを実行します。

  3. 二つ目のexpectでは、Mockを実行した結果、Controllerが実行されてscope内に期待通りphonesモデルが作成されていることをチェックする

このように、AngularのAjaxを活用するend-to-end testsはサーバ側を意識せずに書けることがキーポイントになります。

以上、step-5のレポートになりますが、この節はAngular Services・DI・テストようのMockなどの要素が含まれており盛りだくさんでした。

最後にRESTfulに対して補足します。



 ### 補足1: サーバ側のIF設計をRESTful にする

 

 サーバー側のIF設計をRESTfulにするということは、HTTPの標準ルールであるメソッド(POST/GETなど)とURIとレスポンスコードを利用した統一的なAPIをWebアプリ/サービス側で設計することになります。

 

 RESTfulにすることよるメリットは、

 

 * 全体的にプログラマブルになり、外部連携がしやすくなる

 * Webアプリ/サービスに実装する画面・処理が全てHTTPメソッドとURIで表現できるためCRUDが書きやすい

 * 処理結果をレスポンスコードで表現することで、属人的な実装を回避できる

 * テスタビリティやメンテナンス性の向上

 

 などがあげられると思います。

 ただし、URIを検討する際にチームによっては議論が白熱することがあります。

 リソースの定義から始まり、リソース名の順番など人によって考え方が異なるからです。

 参考文献: RESTful Webサービス

(ここに記述されている通り、RESTで様々な仕組み実現ができる考え方がHTTPの規格にはたくさん盛り込まれることを初めて知りました。これまで200と404と500、GET/POSTしか知らなかったのはかなりもったいなかった。)



補足2: 個人的感想

過去の私のAngular紹介記事でも記述しましたが、本stepのテストで書かれているような仕組みが

個人的に、Angular.jsの一番好感を持てる仕組みがAjaxのサポート方法です。

Ajax含めてJavaScriptは、どんなフレームワークを使ったとしても(もしくは使わなかったとしても)、

たいていの処理は書けます。巷に情報がごろごろと公開されていますし。

JavaScriptの課題点は、サクサクと書いた後の運用にあります。

最近までは、クライアントサイドで動作するJavascript程メンテナンスするのに作業コストのかかる仕組みはないと個人的に思っていました。

複雑化したJavaScript部分に特化した動作確認を行う手立ては、ブラウザを用意してテスト手順などを考えて、環> 境整えて行う必要があります。込み入った不具合などは、手練れのテスターでないとなかなか再現できないことも> あります。

Angularに限りませんが、不具合などはすべてテストコードに落として再帰的にチェックできるようにしておくべきです。実際にAppを稼働させて動作確認できるテストコードにまとめておく方がメンテナンスコストも保全性も確実に上がります。加えて、CIなどでテストが落ちる状態を可視化すると確実に品質の保全に繋がります。

Node.jsとKarmaがあればCIでのテスト自動化は確実にできます。他のJSフレームワークより少し手間を省けるAngularのend-to-end testsとDIを利用すると、ブラウザ操作のテスト手順をテストコード上に実現できます。