web-dev-qa-db-fra.com

Test unitaire de la directive AngularJS avec templateUrl

J'ai une directive AngularJS qui a un templateUrl défini. J'essaie de faire des tests unitaires avec Jasmine.

Mon JavaScript Jasmine ressemble à ce qui suit, selon la recommandation de 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');
            });

        });
    });
});

Lorsque j'exécute ceci dans mon erreur Jasmine Spec, j'obtiens l'erreur suivante:

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

Tout ce que je veux, c'est que templateUrl soit chargé tel quel - je ne veux pas utiliser respond. Je crois que cela peut être lié à cela en utilisant ngMock au lieu de ngMockE2E . Si c'est le coupable, comment utiliser ce dernier à la place de l'ancien?

Merci d'avance!

121
Words Like Jared

Ce que j’ai finalement fait, c’est d’obtenir le cache de modèles et d’y insérer la vue. Je n'ai pas le contrôle sur le fait de ne pas utiliser ngMock, il s'avère:

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

Vous avez raison de dire que c'est lié à ngMock. Le module ngMock est chargé automatiquement pour chaque Angular) et il initialise le modèle $httpBackend pour gérer toute utilisation du $http service, qui inclut la récupération de modèles. Le système de template tente de charger le template via $http et cela devient une "demande inattendue" à la maquette.

Ce dont vous avez besoin pour pré-charger les modèles dans le $templateCache afin qu’ils soient déjà disponibles lorsque Angular les demande, sans utiliser $http.

La solution préférée: le karma

Si vous utilisez Karma pour exécuter vos tests (et vous devriez le faire), vous pouvez le configurer pour charger les modèles pour vous avec le préprocesseur ng-html2js . Ng-html2js lit les fichiers HTML que vous spécifiez et les convertit en un module Angular qui précharge le $templateCache.

Étape 1: Activez et configurez le préprocesseur dans votre 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"
},

Si vous utilisez Yeoman pour échafauder votre application, cette configuration fonctionnera

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

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

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

Étape 2: Utilisez le module dans vos tests

// my-test.js

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

Pour un exemple complet, regardez ceci exemple canonique tiré de Angular test guru Vojta Jina . Il comprend une configuration complète: configuration de karma, modèles et tests.

Une solution non-karma

Si vous n'utilisez pas Karma pour quelque raison que ce soit (j'ai eu un processus de construction inflexible dans une application héritée) et que vous testez simplement dans un navigateur, j'ai découvert que vous pouvez contourner le contrôle de ngMock sur $httpBackend en utilisant un XHR brut pour récupérer le modèle et l’insérer dans le $templateCache. Cette solution est beaucoup moins flexible, mais le travail est fait pour le moment.

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

Sérieusement. Utilisez Karma . La configuration nécessite un peu de travail, mais elle vous permet d'exécuter tous vos tests, dans plusieurs navigateurs à la fois, à partir de la ligne de commande. Vous pouvez donc l'intégrer à votre système d'intégration continue et/ou en faire un raccourci clavier à partir de votre éditeur. Bien mieux que alt-tab-refresh-ad-infinitum.

184
SleepyMurph

Ce problème initial peut être résolu en ajoutant ceci:

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

C'est parce qu'il essaie de trouver le module $ httpBackend dans ngMock et qu'il n'est pas complet.

12
bullgare

La solution à laquelle je parviens a besoin de jasmine-jquery.js et d’un serveur proxy.

J'ai suivi ces étapes:

  1. Dans karma.conf:

ajoutez jasmine-jquery.js à vos fichiers

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

ajoutez un serveur proxy qui servira vos fixtures

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. Dans votre spec

    describe ('MySpec', function () {var $ scope, template; jasmine.getFixtures (). fixturesPath = 'public/partials /'; // chemin personnalisé pour que vous puissiez servir le modèle réel que vous utilisez sur l'application beforeEach (function () {template = 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. Exécuter un serveur sur le répertoire racine de votre application

    python -m SimpleHTTPServer 3502

  3. Courez le karma.

Il a fallu un certain temps pour comprendre cela, étant obligé de rechercher de nombreux articles, je pense que la documentation à ce sujet devrait être plus claire, car il s'agit d'un sujet tellement important.

8
Tomas Romero

Ma solution:

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

le 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

J'ai résolu le même problème d'une manière légèrement différente de la solution choisie.

  1. Tout d'abord, j'ai installé et configuré le plugin ng-html2js pour karma. Dans le fichier 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. Ensuite, j'ai chargé le module créé dans le beforeEach. Dans votre fichier Spec.js:

    beforeEach(module('myApp', 'to/templates/myTemplate.html'));
    
  3. Ensuite, j'ai utilisé $ templateCache.get pour le stocker dans une variable. Dans votre fichier 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. Enfin, je l'ai testé de cette façon. Dans votre fichier Spec.js:

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

Si vous utilisez Grunt, vous pouvez utiliser les modèles grunt-angular. Il charge vos modèles dans le templateCache et est transparent à la configuration de vos spécifications.

Ma configuration d'échantillon:

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

Pour charger dynamiquement le template html dans $ templateCache, vous pouvez simplement utiliser le pré-processeur html2js karma, comme expliqué ici

cela revient à ajouter des modèles '. html' à vos fichiers dans le fichier conf.js, ainsi que des préprocesseurs = {'. html': 'html2js' };

et utilise

beforeEach(module('..'));

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

dans votre fichier de test js

4
Lior

Si vous utilisez requirejs dans vos tests, vous pouvez utiliser le plugin 'text' pour extraire le modèle html et le placer dans $ 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();
    }));
  });
});
2
Tim Kindberg

Si vous utilisez le jasmine-maven-plugin avec RequireJS, vous pouvez utiliser le plugin de texte pour charger le contenu du modèle dans une variable, puis le placer dans le cache du modèle.


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

si vous utilisez Karma, pensez à utiliser karma-ng-html2js-preprocessor pour pré-compiler vos modèles HTML externes et éviter d'avoir Angular essayez de les HTTP GET pendant L’exécution du test, j’ai eu du mal à résoudre ce problème pendant quelques-uns des cas - dans mon cas, les chemins partiels de templateUrl ont été résolus au cours de l’exécution normale de l’application, mais pas au cours des tests - en raison de différences entre les structures app et dir.

2
Nikita

Je résous ce problème en compilant tous les modèles dans templatecache. J'utilise gulp, vous pouvez trouver une solution similaire pour grunt aussi. Mon templateUrls dans les directives, les modaux ressemble à

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. Ajouter un nouveau paquet npm dans mon package.json

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

  2. Dans le fichier gulp, ajoutez templatecache et une nouvelle tâche:

    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. Ajouter tous les fichiers js dans index.html

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

  4. Prendre plaisir!

0
kitolog