ui-router使用時のUnit Test注意事項

このエントリーをはてなブックマークに追加

ui-router使用時にunit testを行ったらよくわからないエラーがkarmaさんから伝えられて割とはまりました。

現象

下記のようにmyAppにhomeというstateを用意していたとします。

angular.module('myApp').config(function ($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/');
    $stateProvider.state('home', {
        url: '/',
        templateUrl: 'home.html',
        controller: 'HomeCtrl as vm'
    });
});

そしてこのアプリに以下の様なserviceがあったとして

class UsersSvc {
  constructor(private $http:ng.IHttpService) {}
  fetch():ng.IHttpPromise<IUser> {
    return this.$http.get('/url/to/users');
  }
}
angular.module('myApp').service('UsersSvc', UsersSvc);

テストコードを以下のように書きます。「UsersSvcのfetchメソッド実行したら1個ユーザが返ってくるよね」という簡単なテストです。

describe('usersSvc', function () {
    beforeEach(module('myApp'));
    var UsersSvc, $httpBackend;
    beforeEach(inject(function (_UsersSvc_, _$httpBackend_) {
        UsersSvc = _UsersSvc_;
        $httpBackend = _$httpBackend_;
    }));

    afterEach(function () {
        $httpBackend.flush();
    });

    describe('#fetch', function () {
        beforeEach(function () {
            $httpBackend.expect('GET', '/url/to/users')
                .respond([{id: 'someUserId'}])
        });
        it('gets 1 user', function () {
            UsersSvc.fetch().success(function (users) {
                expect(users).to.have.length(1);
            })
        });
    });
});

簡単なテストなのでパスするかと思いきや「karma start」するとエラーが出てきます。

PhantomJS 1.9.8 (Mac OS X) usersSvc "after each" hook FAILED
        Error: Unexpected request: GET home.html
        Expected GET /url/to/users

原因は
Karma test breaks after using ui-router
にて説明されています。
どうやら$stateサービス使用時には、初期化の段階でtemplateの読み込みを試みるみたいです。今回の場合は

$stateProvider.state('home', {
    url: '/',
    templateUrl: 'home.html',
    controller: 'HomeCtrl as vm'
});

上記の設定が行われている為にhome.htmlに対して知らないうちにGETが発行されているんですね。そしてこのGETが実際に実行されるのがテストケースの中のafterEach内で行われている$httpBackend.flush()のときです。これが

beforeEach(function () {
    $httpBackend.expect('GET', '/url/to/users')
        .respond([{id: 'someUserId'}])
});

とURLが不一致になる為、テスト失敗になっちゃうんですね。

対処法

$templateCacheに設定

手動でこの問題を回避する方法に$templateCacheに該当するtemplateを設定することです。今回のケースでいけば下のように$templateCache.putを使ってhome.htmlを設定できます。$templateCacheに設定しておくことでGETが発行されなくなるという仕組みです。(多分)

beforeEach(inject(function (_UsersSvc_, _$httpBackend_, $templateCache) {
    UsersSvc = _UsersSvc_;
    $httpBackend = _$httpBackend_;
    $templateCache.put('home.html','<div>blank or whatever</div>');
}));

karma-ng-html2js-preprocessorで自動化

karma-ng-html2js-preprocessorという便利なツールがあります。指定したテンプレを$templateCacheに自動で設定してくれます。
まずはインストールしましょう。

npm install --save-dev karma-ng-html2js-preprocessor

次にkarma.conf.jsの設定です。以下、ポイントとなる部分しか記述していません。実際のkarma.conf.jsはテストをするための諸々の設定が必要です。また、ngHtml2JsPreprocessorの設定も各環境に合わせて設定してください。筆者の場合「www/」の直下にしかtemplateを置いていないので以下のような設定になっています。

module.exports = function (config) {
    config.set({
        preprocessors: {
            'www/*.html': ['ng-html2js']
        },

        files: [
            'www/*.html',
        ],

        ngHtml2JsPreprocessor: {
            // ファイルパスの先頭から取り除く文字列
            stripPrefix: 'www/',
            // ファイルパスの末尾から取り除く文字列
            //stripSufix: '.ext',
            // パスの先頭に付加する文字列
            //prependPrefix: 'served/',

            // or define a custom transform function
            //cacheIdFromPath: function(filepath) {
            //    return cacheId;
            //},

            // moduleNameを指定することでこのモジュールを読み込むだけでtemplateを全て読み込むことが可能
            moduleName: 'templates'
        },
    })
};

ここまで設定が完了しましたらテスト時にmoduleを読み込むだけでテストは意図通りに実行されるようになります。

beforeEach(function () {
    module('myApp');
    module('templates'); // ここを追加
});
Written on January 31, 2015
このエントリーをはてなブックマークに追加