AngularJSでコントローラからREST APIを叩き、JSONデータを取得することがよくあると思う。バックエンドにモックを使用してJasmineにてテストする方法を備忘録も兼ねて記述しておく。
目次
環境
- AngularJS 1.3.4
- Jasmine 2.1.2
APIアクセスにモックを使用する
テストの対象となるコントローラは次のような感じ。
📄posts_ctrl.coffee
angular.module('App') .controller 'PostsCtrl', ($scope, Post) -> $scope.posts = Post.query()
よくある感じ。Postモデルも典型的で、REST APIにてバックエンドからJSONを取得する。
📄post.coffee
angular.module('App') .factory 'Post', ($resource) -> $resource '/api/posts'
このコントローラをJasmineでユニットテストするのだが、このバックエンドへのアクセスをモック化する。$httpBackendをインジェクションすればよい。
📄posts_ctrl_spec.coffee
'use strict' describe 'PostsCtrl', -> beforeEach -> module 'App' beforeEach inject ($httpBackend, $controller, $rootScope, Post) -> @$scope = $rootScope.$new() # REST APIアクセスをモック化する $httpBackend.when('GET', '/api/posts').respond([{ mock: 'spec' }]) $controller('PostsCtrl', { $scope: @$scope, Post: Post }) $httpBackend.flush() it "assigns posts to $scope", -> expect(@$scope.posts).toEqual([{ mock: 'spec' }])
よしよし、これでテストに成功す・・・・るはずが失敗してしまった。
📄posts_ctrl_spec.coffee
PhantomJS 1.9.8 (Mac OS X) Jasmine__TopLevel__Suite PickupCtrl assigns data tweets to $scope FAILED Expected [ { mock: 'spec' } ] to equal [ { mock: 'spec' } ]. Error: Expected [ { mock: 'spec' } ] to equal [ { mock: 'spec' } ]. ...
{ mock: 'spec' }
って、バッチリ一致してるじゃん・・と思いきや、JasmineのtoEqual
メソッドはJSON同士の比較はできないらしい。ちぇっ、気が利かないなあ。
しょうがないので、angularのequalsメソッドを使用するように、カスタムマッチャを追加する。
describe 'PickupCtrl', -> # カスタムマッチャ beforeEach -> jasmine.addMatchers toEqualData: (util, customEqualityTesters) -> compare: (actual, expected) -> { pass: angular.equals(actual, expected) } ... it "assigns data tweets to $scope", -> expect(@$scope.dataTweets).toEqualData([{ mock: 'spec' }])
toEqualData
という少々ダサネームのマッチャを追加した。これでテストに成功するはず。
ちょっとリファクタリング
このままでもよいのだが、この手のカスタムマッチャは色んなテストから使用すると思うので、別ファイルに定義しておきたい。spec_helper.coffee
というファイルを作成して、そのファイルにまとめて記述しておこう。
📄spec_helper.coffee
# カスタムマッチャ beforeEach -> jasmine.addMatchers toEqualData: (util, customEqualityTesters) -> compare: (actual, expected) -> { pass: angular.equals(actual, expected) } # モジュール beforeEach -> module 'App'
1モジュールで構成するアプリなら、上記のようにspec_helperで読み込んじゃってもいいと思う。
requireを記述すれば、テストコードが少しスッキリするはずだ。
📄posts_ctrl_spec.coffee
#= require spec_helper 'use strict' describe 'PickupCtrl', -> beforeEach inject ($httpBackend, $controller, $rootScope, Post) -> @$scope = $rootScope.$new() $httpBackend.when('GET', '/api/posts').respond([{ mock: 'spec' }]) $controller('PostsCtrl', { $scope: @$scope, Post: Post }) $httpBackend.flush() it "assigns posts to $scope", -> expect(@$scope.posts).toEqualData([{ mock: 'spec' }])
うむ、今日のところはコレで勘弁しといてやろう!
参考サイト
関連する記事
- Rails+JSフレームワークでリアルタイム掲示板を作成してみる(AngularJS編)
- テスト時にangular-translateによるリクエストをスルーする
- AngularJS + Railsで国際化(i18n)
- How to keep scroll position after refreshing in AngularJS
- Rails+JSフレームワークでリアルタイム掲示板を作成してみる(Backbone.js編)