templateUrlを使用したAngularJSディレクティブの単体テスト


122

templateUrl定義されているAngularJSディレクティブがあります。私はそれをジャスミンでユニットテストしようとしています。

私のJasmine JavaScriptは、この推奨に従って、次のようになります。

describe('module: my.module', function () {
    beforeEach(module('my.module'));

    describe('my-directive directive', function () {
        var scope, $compile;
        beforeEach(inject(function (_$rootScope_, _$compile_, $injector) {
            scope = _$rootScope_;
            $compile = _$compile_;
            $httpBackend = $injector.get('$httpBackend');
            $httpBackend.whenGET('path/to/template.html').passThrough();
        }));

        describe('test', function () {
            var element;
            beforeEach(function () {
                element = $compile(
                    '<my-directive></my-directive>')(scope);
                angular.element(document.body).append(element);
            });

            afterEach(function () {
                element.remove();
            });

            it('test', function () {
                expect(element.html()).toBe('asdf');
            });

        });
    });
});

Jasmine specエラーでこれを実行すると、次のエラーが発生します。

TypeError: Object #<Object> has no method 'passThrough'

必要なのは、templateUrlをそのままロードすることだけrespondです。使用したくありません。これは、ngMockE2Eの代わりにngMockを使用することに関連している可能性があると思います。これが原因である場合、前者の代わりに後者をどのように使用しますか?

前もって感謝します!


1
私は.passThrough();そのように使用していませんが、ドキュメントから、次のようなことを試しましたか$httpBackend.expectGET('path/to/template.html'); // do action here $httpBackend.flush();?これはあなたの使用法に適していると思います-要求をキャッチしたくない、つまりwhenGet()、それが送信されていることを確認してから、実際にそれを送る?
Alex Osborn 2013年

1
返信いただきありがとうございます。私はそれがexpectGET要求を送るとは思わない...少なくとも箱から出して。ではドキュメントとの例が/auth.pyあり$httpBackend.when前に$httpBackend.expectGETして$httpBackend.flushのコール。
Jared

2
正解expectGetです。リクエストが試行されたかどうかを確認するだけです。
Alex Osborn 2013年

1
ああ。まあ私は$httpBackendモックにディレクティブの下で提供されたURLを実際に使用してtemplateUrlそれを取得するように伝える方法が必要です。passThroughこれをやろうと思った。これを行う別の方法を知っていますか?
Jared

2
うーん、まだe2eテストをあまり行っていませんが、ドキュメントをチェックしています-e2eバックエンドを代わりに使用してみましたか- それが
Alexオズボーン2013年

回答:


187

ngMockに関連していることは間違いありません。ngMockモジュールはすべてのAngularテストに対して自動的にロードされ、テンプレートのフェッチを含むサービスの$httpBackend使用を処理するためにモックを初期化します$http。テンプレートシステムはテンプレートをロードしようと$httpし、モックへの「予期しない要求」になります。

テンプレートをに事前にロードして、$templateCacheAngularから要求されたときに、を使用せずにテンプレートを利用できるようにするために必要なもの$http

推奨されるソリューション:Karma

Karmaを使用してテストを実行している場合は(そうする必要があります)、ng-html2jsプリプロセッサーを使用してテンプレートをロードするように構成できます。Ng-html2jsは指定されたHTMLファイルを読み取り、それらををプリロードするAngularモジュールに変換します$templateCache

ステップ1:プリプロセッサを有効にして構成する karma.conf.js

// karma.conf.js

preprocessors: {
    "path/to/templates/**/*.html": ["ng-html2js"]
},

ngHtml2JsPreprocessor: {
    // If your build process changes the path to your templates,
    // use stripPrefix and prependPrefix to adjust it.
    stripPrefix: "source/path/to/templates/.*/",
    prependPrefix: "web/path/to/templates/",

    // the name of the Angular module to create
    moduleName: "my.templates"
},

Yeomanを使用してアプリを足場している場合、この構成は機能します

plugins: [ 
  'karma-phantomjs-launcher', 
  'karma-jasmine', 
  'karma-ng-html2js-preprocessor' 
], 

preprocessors: { 
  'app/views/*.html': ['ng-html2js'] 
}, 

ngHtml2JsPreprocessor: { 
  stripPrefix: 'app/', 
  moduleName: 'my.templates' 
},

ステップ2:テストでモジュールを使用する

// my-test.js

beforeEach(module("my.templates"));    // load new module containing templates

完全な例については、Angularテストの第一人者Vojta Jinaによるこの正規の例をご覧ください。これには、karma構成、テンプレート、テストなどのセットアップ全体が含まれます。

非カルマソリューション

何らかの理由でKarmaを使用せず(レガシーアプリに柔軟性のないビルドプロセスがあった)、ブラウザーでテストしているだけの$httpBackend場合、生のXHRを使用してテンプレートを実際にフェッチすることで、ngMockの乗っ取りを回避できることがわかりましたに挿入し$templateCacheます。このソリューションは柔軟性がはるかに低くなりますが、今のところ仕事は完了しています。

// my-test.js

// Make template available to unit tests without Karma
//
// Disclaimer: Not using Karma may result in bad karma.
beforeEach(inject(function($templateCache) {
    var directiveTemplate = null;
    var req = new XMLHttpRequest();
    req.onload = function() {
        directiveTemplate = this.responseText;
    };
    // Note that the relative path may be different from your unit test HTML file.
    // Using `false` as the third parameter to open() makes the operation synchronous.
    // Gentle reminder that boolean parameters are not the best API choice.
    req.open("get", "../../partials/directiveTemplate.html", false);
    req.send();
    $templateCache.put("partials/directiveTemplate.html", directiveTemplate);
}));

でも真剣に。Karmaを使用します。設定には少し手間がかかりますが、コマンドラインから複数のブラウザで一度にすべてのテストを実行できます。したがって、継続的インテグレーションシステムの一部として使用したり、エディターからショートカットキーにすることができます。alt-tab-refresh-ad-infinitumよりもはるかに優れています。


6
これは明らかかもしれませんが、他の人が同じことにこだわってここで答えを探している場合:preprocessorsファイルパターン(など"path/to/templates/**/*.html")をのfilesセクションに追加しないと、それを機能させることができませんでしたkarma.conf.js
ヨハン

1
それで、続行する前に応答を待たないことで大きな問題はありますか?リクエストが戻ってきたときに値を更新するだけですか(IEには30秒かかります)?
ジャッキー

1
@ジャッキーfalseXHRのopen呼び出しのパラメーターを使用して同期させる「非カルマ」の例について話をしていると思います。これを行わないと、テンプレートがロードされていなくても、実行は陽気に続行してテストの実行を開始します。これにより、同じ問題に戻ることができます。1)テンプレートのリクエストが送信されなくなります。2)テストの実行が開始されます。3)テストはディレクティブをコンパイルしますが、テンプレートはまだロードされていません。4)Angular $httpはモックアウトされたサービスを通じてテンプレートをリクエストします。5)モック$httpサービスは「予期しない要求」と不平を言います。
SleepyMurph 2014

1
私はカルマなしでうなり声ジャスミンを実行することができました。
FlavorScape 14

5
もう1つ:stackoverflow.com/a/19077966/859631によると、karma-ng-html2js-preprocessor(npm install --save-dev karma-ng-html2js-preprocessor)をインストールし、のプラグインセクションに追加する必要があります。karma.conf.js
Vincent

37

私がやったことは、テンプレートキャッシュを取得してビューをそこに配置することでした。私はngMockを使用しないことを制御できません、それは判明しました:

beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
    $scope = _$rootScope_;
    $compile = _$compile_;
    $templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));

26
これがこのメソッドの私の不満です...次に、テンプレートキャッシュに文字列として挿入する大きなhtmlを作成する場合、フロントエンドでhtmlを変更するときに何をするのでしょうか。 ?テストのhtmlも変更しますか?IMOは持続不可能な回答であり、templateUrlオプションよりもtemplateを使用した理由です。ディレクティブで大量の文字列としてhtmlを使用することは非常に嫌いですが、2か所のhtmlを更新する必要がないための最も持続可能なソリューションです。これは、時間が経つにつれてHTMLが一致しなくなる可能性のある多くの画像を取得しません。
Sten Muchow 2014

12

この最初の問題は、これを追加することで解決できます。

beforeEach(angular.mock.module('ngMockE2E'));

これは、デフォルトでngMockモジュールで$ httpBackendを検索しようとするためであり、完全ではありません。


1
まあ、それは確かに元の質問に対する正解です(それが私を助けたものです)。
Mat

これを試しましたが、passThrough()はまだ機能しませんでした。それでも、「予期しない要求」エラーが発生しました。
frodo2975

8

私が到達したソリューションには、jasmine-jquery.jsとプロキシサーバーが必要です。

私はこれらのステップに従いました:

  1. karma.conf内:

jasmine-jquery.jsをファイルに追加します

files = [
    JASMINE,
    JASMINE_ADAPTER,
    ...,
    jasmine-jquery-1.3.1,
    ...
]

フィクスチャをサーバーするプロキシサーバーを追加する

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. あなたのスペックで

    describe( 'MySpec'、function(){var $ scope、template; jasmine.getFixtures()。fixturesPath = 'public / partials /'; //アプリで使用する実際のテンプレートを提供できるようにカスタムパスbeforeEach(function (){テンプレート= angular.element( '');

        module('project');
        inject(function($injector, $controller, $rootScope, $compile, $templateCache) {
            $templateCache.put('partials/resources-list.html', jasmine.getFixtures().getFixtureHtml_('resources-list.html')); //loadFixture function doesn't return a string
            $scope = $rootScope.$new();
            $compile(template)($scope);
            $scope.$apply();
        })
    });

    });

  2. アプリのルートディレクトリでサーバーを実行する

    python -m SimpleHTTPServer 3502

  3. カルマを実行します。

これを理解するにはしばらく時間がかかりました。多くの投稿を検索する必要がありました。これは非常に重要な問題であるため、これに関するドキュメントはより明確になるはずです。


実行中の修正済みのlocalhost/base/specsプロキシサーバーを追加してアセットを提供する際に問題が発生しましpython -m SimpleHTTPServer 3502た。あなたは天才です!
pbojinov 2013

テストで$ compileから空の要素が返されました。他の場所は$ scope。$ digest()の実行を提案しました:まだ空です。$ scope。$ apply()を実行してもうまくいきました。ディレクティブでコントローラーを使用しているためだと思いますか?わからない。アドバイスありがとうございます!助けた!
Sam Simmons

7

私の解決策:

test/karma-utils.js

function httpGetSync(filePath) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/base/app/" + filePath, false);
  xhr.send();
  return xhr.responseText;
}

function preloadTemplate(path) {
  return inject(function ($templateCache) {
    var response = httpGetSync(path);
    $templateCache.put(path, response);
  });
}

karma.config.js

files: [
  //(...)
  'test/karma-utils.js',
  'test/mock/**/*.js',
  'test/spec/**/*.js'
],

テスト:

'use strict';
describe('Directive: gowiliEvent', function () {
  // load the directive's module
  beforeEach(module('frontendSrcApp'));
  var element,
    scope;
  beforeEach(preloadTemplate('views/directives/event.html'));
  beforeEach(inject(function ($rootScope) {
    scope = $rootScope.$new();
  }));
  it('should exist', inject(function ($compile) {
    element = angular.element('<event></-event>');
    element = $compile(element)(scope);
    scope.$digest();
    expect(element.html()).toContain('div');
  }));
});

開発者にKarmaの使用を強制しない最初のまともなソリューション。どうして角張った男たちは、とてもクールな何かの真ん中で何か悪いことをしたり、簡単に回避できるのでしょうか?pfff
Fabio Milheiro、2014年

'test / mock / ** / *。js'を追加したようですが、サービスなどすべてのモックされたものをロードするためだと思いますか?モックされたサービスのコードの重複を回避する方法を探しています。それについてもう少し教えていただけますか?
ステファン

正確には覚えていませんが、$ httpサービスのJSONの例にはおそらく設定がありました。派手なものは何もありません。
bartek 2015

今日この問題を抱えていた-素晴らしい解決策。私たちはカルマを使用しますが、Cutzpahも使用します。カルマを使用せざるを得ない理由はなく、ディレクティブを単体テストできるのはカルマだけです。
lwalden 2015

私たちは、角度とジャンゴを使用しているが、これはディレクティブをテストするために魔法のように働いている負荷のtemplateUrlけれどもstatic、例えばbeforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); ありがとう!
Aleck Landgraf、2015

6

Gruntを使用している場合は、grunt-angular-templatesを使用できます。テンプレートをtemplateCacheにロードし、スペック構成に対して透過的です。

私のサンプル設定:

module.exports = function(grunt) {

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    ngtemplates: {
        myapp: {
          options: {
            base:       'public/partials',
            prepend:    'partials/',
            module:     'project'
          },
          src:          'public/partials/*.html',
          dest:         'spec/javascripts/angular/helpers/templates.js'
        }
    },

    watch: {
        templates: {
            files: ['public/partials/*.html'],
            tasks: ['ngtemplates']
        }
    }

  });

  grunt.loadNpmTasks('grunt-angular-templates');
  grunt.loadNpmTasks('grunt-contrib-watch');

};

6

選択したソリューションとは少し異なる方法で同じ問題を解決しました。

  1. まず、カルマ用のng-html2jsプラグインをインストールして構成しました。karma.conf.jsファイルで:

    preprocessors: {
      'path/to/templates/**/*.html': 'ng-html2js'
    },
    ngHtml2JsPreprocessor: {
    // you might need to strip the main directory prefix in the URL request
      stripPrefix: 'path/'
    }
  2. 次にbeforeEachで作成したモジュールをロードしました。Spec.jsファイルで:

    beforeEach(module('myApp', 'to/templates/myTemplate.html'));
  3. 次に、$ templateCache.getを使用してそれを変数に格納しました。Spec.jsファイルで:

    var element,
        $scope,
        template;
    
    beforeEach(inject(function($rootScope, $compile, $templateCache) {
      $scope = $rootScope.$new();
      element = $compile('<div my-directive></div>')($scope);
      template = $templateCache.get('to/templates/myTemplate.html');
      $scope.$digest();
    }));
  4. 最後に、この方法でテストしました。Spec.jsファイルで:

    describe('element', function() {
      it('should contain the template', function() {
        expect(element.html()).toMatch(template);
      });
    });

4

テンプレートhtmlを$ templateCacheに動的にロードするには、ここで説明するように、html2jsカルマプリプロセッサを使用するだけです。

これはつまり、conf.jsファイルのファイルにテンプレート ' .html'を追加することと、プリプロセッサ= {' .html': 'html2js'};

そして使う

beforeEach(module('..'));

beforeEach(module('...html', '...html'));

あなたのjsテストファイルに


取得中Uncaught SyntaxError: Unexpected token <
Melbourne2991

2

Karmaを使用している場合は、karma-ng-html2js-preprocessorを使用して外部HTMLテンプレートをプリコンパイルし、テストの実行中にAngularがHTTP GETを試行しないようにすることを検討してください。アプリとテストのディレクトリ構造の違いにより、通常のアプリの実行中にtemplateUrlの部分パスが解決され、テスト中には解決されなかったため、私はこれに苦労しました。


2

あなたが使用している場合はジャスミンのmaven-pluginの RequireJSと一緒に、あなたは使うことができ、テキストプラグインを変数にテンプレートのコンテンツをロードした後、テンプレートキャッシュに入れます。


define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
    "use strict";

    describe('Directive TestSuite', function () {

        beforeEach(inject(function( $templateCache) {
            $templateCache.put("path/to/template.html", directiveTemplate);
        }));

    });
});

Karmaなしでこれを行うことができますか?
Winnemucca 2016

2

テストでrequirejsを使用する場合は、 'text'プラグインを使用してhtmlテンプレートをプルし、$ templateCacheに配置できます。

require(["text!template.html", "module-file"], function (templateHtml){
  describe("Thing", function () {

    var element, scope;

    beforeEach(module('module'));

    beforeEach(inject(function($templateCache, $rootScope, $compile){

      // VOILA!
      $templateCache.put('/path/to/the/template.html', templateHtml);  

      element = angular.element('<my-thing></my-thing>');
      scope = $rootScope;
      $compile(element)(scope);   

      scope.$digest();
    }));
  });
});

0

すべてのテンプレートをtemplatecacheにコンパイルすることで、この問題を解決します。私はgulpを使用していますが、うなり声にも同様の解決策を見つけることができます。ディレクティブのtemplateUrls、モーダルは次のようになります

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. 私のpackage.jsonに新しいnpmパッケージを追加します

    "gulp-angular-templatecache": "1.*"

  2. gulpファイルにtemplatecacheと新しいタスクを追加します。

    var templateCache = require('gulp-angular-templatecache'); ... ... gulp.task('compileTemplates', function () { gulp.src([ './app/templates/**/*.html' ]).pipe(templateCache('templates.js', { transformUrl: function (url) { return '/templates/' + url; } })) .pipe(gulp.dest('wwwroot/assets/js')); });

  3. index.htmlにすべてのjsファイルを追加します

    <script src="/assets/js/lib.js"></script> <script src="/assets/js/app.js"></script> <script src="/assets/js/templates.js"></script>

  4. 楽しい!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.