web-dev-qa-db-de.com

Unit Testing AngularJS Direktive mit templateUrl

Ich habe eine AngularJS-Direktive, für die ein templateUrl definiert ist. Ich versuche es mit Jasmine zu testen.

Mein Jasmin JavaScript sieht wie folgt aus, laut Empfehlung von this :

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');
            });

        });
    });
});

Wenn ich dies in meinem Jasmine-Spezifikationsfehler ausführe, erhalte ich den folgenden Fehler:

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

Ich möchte nur, dass die templateUrl so geladen wird, wie sie ist - ich möchte respond nicht verwenden. Ich glaube, dies könnte damit zusammenhängen, dass ngMock anstelle von ngMockE2E verwendet wird. Wenn dies der Schuldige ist, wie verwende ich den letzteren anstelle des ersteren?

Danke im Voraus!

121

Am Ende habe ich den Vorlagen-Cache abgerufen und die Ansicht dort eingefügt. Ich habe keine Kontrolle darüber, ob ich ngMock nicht benutze.

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

Sie haben Recht, dass es mit ngMock zusammenhängt. Das ngMock-Modul wird automatisch für jeden Angular Test) geladen und initialisiert den Schein $httpBackend, Um jede Verwendung des $http - Dienstes, der das Abrufen von Vorlagen umfasst, zu verarbeiten. Das Template-System versucht, das Template über $http Zu laden und es wird zu einer "unerwarteten Anfrage" an den Mock.

Was Sie brauchen, ist eine Möglichkeit, die Vorlagen in den $templateCache Vorzuladen, damit sie bereits verfügbar sind, wenn Angular nach ihnen fragt, ohne $http Zu verwenden.

Die bevorzugte Lösung: Karma

Wenn Sie Karma verwenden, um Ihre Tests auszuführen (und das sollten Sie auch tun), können Sie es so konfigurieren, dass die Vorlagen mit dem Präprozessor ng-html2js geladen werden. Ng-html2js liest die von Ihnen angegebenen HTML-Dateien und konvertiert sie in ein Angular Modul, das den $templateCache Vorlädt.

Schritt 1: Aktivieren und konfigurieren Sie den Präprozessor in Ihrem 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"
},

Wenn Sie Yeoman verwenden, um Ihre App zu rüsten, funktioniert diese Konfiguration

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

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

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

Schritt 2: Verwenden Sie das Modul in Ihren Tests

// my-test.js

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

Ein vollständiges Beispiel finden Sie hier Kanonisches Beispiel von Angular Testguru Vojta Jina . Es enthält ein vollständiges Setup: Karma-Konfiguration, Vorlagen und Tests.

Eine Nicht-Karma-Lösung

Wenn Sie Karma aus irgendeinem Grund nicht verwenden (ich hatte einen unflexiblen Build-Prozess in einer Legacy-App) und teste nur in einem Browser, habe ich festgestellt, dass Sie die Übernahme von $httpBackend Durch ngMock mit einem rohen XHR umgehen können um die Vorlage abzurufen und in das Feld $templateCache einzufügen. Diese Lösung ist viel weniger flexibel, erledigt aber vorerst die Arbeit.

// 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);
}));

Aber im Ernst. Verwenden Sie Karma . Die Einrichtung ist ein wenig aufwändig, aber Sie können alle Ihre Tests in mehreren Browsern gleichzeitig über die Befehlszeile ausführen. Sie können es also als Teil Ihres kontinuierlichen Integrationssystems verwenden und/oder es als Tastenkürzel in Ihrem Editor verwenden. Viel besser als Alt-Tab-Refresh-Ad-Infinitum.

184
SleepyMurph

Dieses anfängliche Problem kann gelöst werden, indem Folgendes hinzugefügt wird:

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

Das liegt daran, dass versucht wird, $ httpBackend im ngMock Modul zu finden und es ist nicht voll.

12
bullgare

Die Lösung, die ich erreicht habe, benötigt jasmine-jquery.js und einen Proxy-Server.

Ich bin diesen Schritten gefolgt:

  1. In der karma.conf:

fügen Sie jasmine-jquery.js zu Ihren Dateien hinzu

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

fügen Sie einen Proxy-Server hinzu, der Ihre Fixtures verwaltet

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. In Ihrer Spezifikation

    describe ('MySpec', function () {var $ scope, template; jasmine.getFixtures (). fixturesPath = 'public/partials /'; // benutzerdefinierter Pfad, damit Sie die echte Vorlage bereitstellen können, die Sie in der App vor jeder Funktion verwenden () {template = angle.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. Führen Sie einen Server im Stammverzeichnis Ihrer App aus

    python -m SimpleHTTPServer 3502

  3. Run Karma.

Es hat eine Weile gedauert, bis ich herausgefunden hatte, dass ich viele Beiträge durchsuchen musste. Ich denke, die Dokumentation darüber sollte klarer sein, da es sich um ein so wichtiges Thema handelt.

8
Tomas Romero

Meine Lösung:

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'
],

der Test:

'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');
  }));
});
7
bartek

Ich habe das gleiche Problem auf eine etwas andere Weise gelöst als die gewählte Lösung.

  1. Zuerst habe ich das ng-html2js Plugin für Karma installiert und konfiguriert. In der Datei 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. Dann habe ich das in beforeEach erstellte Modul geladen. In Ihrer Spec.js-Datei:

    beforeEach(module('myApp', 'to/templates/myTemplate.html'));
    
  3. Dann habe ich $ templateCache.get verwendet, um es in einer Variablen zu speichern. In Ihrer Spec.js-Datei:

    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. Zum Schluss habe ich es so getestet. In Ihrer Spec.js-Datei:

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

Wenn Sie Grunt verwenden, können Sie Grunt-Angular-Templates verwenden. Es lädt Ihre Vorlagen in den templateCache und ist transparent für Ihre Spezifikationskonfiguration.

Meine Beispielkonfiguration:

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
Tomas Romero

Um die HTML-Vorlage dynamisch in $ templateCache zu laden, können Sie einfach den HTML2J-Karma-Vorprozessor verwenden, wie in hier erklärt

dies läuft darauf hinaus, Vorlagen '. html' zu Ihren Dateien in der Datei conf.js sowie Präprozessoren = {'. html': 'html2js' hinzuzufügen. };

und verwenden

beforeEach(module('..'));

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

in deine js testing datei

4
Lior

Wenn Sie in Ihren Tests requirejs verwenden, können Sie das 'text'-Plugin verwenden, um die HTML-Vorlage in den $ templateCache zu laden.

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();
    }));
  });
});
2
Tim Kindberg

Wenn Sie das jasmine-maven-plugin zusammen mit RequireJS verwenden, können Sie das text plugin verwenden, um den Vorlageninhalt in eine Variable zu laden und ihn dann in den Vorlagencache zu legen.


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);
        }));

    });
});
2

wenn Sie Karma verwenden, ziehen Sie die Verwendung von karma-ng-html2js-preprocessor in Betracht, um Ihre externen HTML-Vorlagen vorzukompilieren, und vermeiden Sie, dass Angular versucht, sie während der Testausführung mit HTTP GET abzurufen. Ich hatte einige Probleme damit - in meinem Fall wurden die Teilpfade von templateUrl während der normalen App-Ausführung aufgelöst, aber nicht während der Tests - aufgrund von Unterschieden in den App- und Testverzeichnisstrukturen.

2
Nikita

Ich behebe dieses Problem, indem ich alle Vorlagen in templatecache kompiliere. Ich benutze gulp, du kannst auch eine ähnliche Lösung für grunzen finden. Meine templateUrls in Direktiven, Modals sieht aus wie

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. Füge ein neues npm-Paket in meine package.json hinzu

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

  2. In gulp file füge templatecache und eine neue Aufgabe hinzu:

    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. Fügen Sie alle js-Dateien in index.html hinzu

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

  4. Genießen!

0
kitolog