Diretiva AngularJS de Testes Unitários com templateUrl

Eu tenho uma diretiva AngularJS que tem um templateUrl definido. Estou tentando testá-lo com Jasmine.

My Jasmine JavaScript se parece com o seguinte, de acordo com a recomendação:

 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( '')(scope); angular.element(document.body).append(element); }); afterEach(function () { element.remove(); }); it('test', function () { expect(element.html()).toBe('asdf'); }); }); }); }); 

Quando executo isso no meu erro de especificação do Jasmine, recebo o seguinte erro:

 TypeError: Object # has no method 'passThrough' 

Tudo que eu quero é que o templateUrl seja carregado como está – não quero usar o respond . Eu acredito que isso pode estar relacionado a ele usando ngMock em vez de ngMockE2E . Se este é o culpado, como eu uso o último em vez do primeiro?

Desde já, obrigado!

Você está certo de que está relacionado ao ngMock. O módulo ngMock é carregado automaticamente para cada teste angular e inicializa o mock $httpBackend para lidar com qualquer uso do serviço $http , que inclui a busca de modelos. O sistema de templates tenta carregar o template através de $http e se torna um “pedido inesperado” para o mock.

O que você precisa fazer é pré-carregar os modelos no $templateCache para que eles já estejam disponíveis quando o Angular os solicitar, sem usar $http .

A solução preferida: Karma

Se você estiver usando o Karma para executar seus testes (e você deve estar), você pode configurá-lo para carregar os modelos para você com o pré – processador ng-html2js . Ng-html2js lê os arquivos HTML especificados e os converte em um módulo Angular que pré-carrega o $templateCache .

Etapa 1: Habilite e configure o pré-processador no seu 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" }, 

Se você estiver usando o Yeoman para montar seu aplicativo, esta configuração funcionará

 plugins: [ 'karma-phantomjs-launcher', 'karma-jasmine', 'karma-ng-html2js-preprocessor' ], preprocessors: { 'app/views/*.html': ['ng-html2js'] }, ngHtml2JsPreprocessor: { stripPrefix: 'app/', moduleName: 'my.templates' }, 

Etapa 2: use o módulo em seus testes

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

Para um exemplo completo, veja este exemplo canônico do guru do teste Angular Vojta Jina . Inclui uma configuração inteira: configuração de karma, modelos e testes.

Uma solução não-karma

Se você não usa o Karma por alguma razão (eu tive um processo de construção inflexível no aplicativo legado) e está apenas testando em um navegador, descobri que você pode contornar a aquisição do $httpBackend do $httpBackend usando um XHR bruto para buscar o modelo para real e insira-o no $templateCache . Essa solução é muito menos flexível, mas o trabalho é feito por enquanto.

 // 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ério, no entanto. Use Karma . Demora um pouco de trabalho para configurar, mas permite executar todos os seus testes, em vários navegadores ao mesmo tempo, a partir da linha de comando. Assim, você pode tê-lo como parte do seu sistema de continuous integration e / ou pode torná-lo uma tecla de atalho do seu editor. Muito melhor que alt-tab-refresh-ad-infinitum.

O que acabei fazendo foi pegar o cache do template e colocar a view lá dentro. Eu não tenho controle sobre não usar o ngMock, acontece:

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

Este problema inicial pode ser resolvido adicionando isto:

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

Isso é porque ele tenta encontrar $ httpBackend no módulo ngMock por padrão e não está cheio.

A solução que alcancei precisa do jasmine-jquery.js e de um servidor proxy.

Eu segui estes passos:

  1. No karma.conf:

adicione jasmine-jquery.js aos seus arquivos

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

adicionar um servidor proxy que irá server seus equipamentos

 proxies = { '/' : 'http://localhost:3502/' }; 
  1. Na sua especificação

    Descreva (‘MySpec’, function () {var $ scope, modelo; jasmine.getFixtures (). fixturesPath = ‘public / partials /’; // caminho personalizado para que você possa servir o modelo real que você usa no aplicativo 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. Executar um servidor no diretório raiz do seu aplicativo

    python -m SimpleHTTPServer 3502

  3. Execute karma.

Demorei um pouco para descobrir isso, ter que procurar muitos posts, acho que a documentação sobre isso deve ser mais clara, pois é uma questão tão importante.

Minha solução:

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

o teste:

 '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(''); element = $compile(element)(scope); scope.$digest(); expect(element.html()).toContain('div'); })); }); 

Se você estiver usando o Grunt, você pode usar grunhido-angular-templates. Ele carrega seus modelos no templateCache e é transparente à sua configuração de especificações.

Minha configuração de amostra:

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

Eu resolvi o mesmo problema de uma maneira ligeiramente diferente da solução escolhida.

  1. Primeiro instalei e configurei o plugin ng-html2js para karma. No arquivo 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. Então eu carreguei o módulo criado no beforeEach. No seu arquivo Spec.js:

     beforeEach(module('myApp', 'to/templates/myTemplate.html')); 
  3. Então eu usei $ templateCache.get para armazená-lo em uma variável. No seu arquivo Spec.js:

     var element, $scope, template; beforeEach(inject(function($rootScope, $compile, $templateCache) { $scope = $rootScope.$new(); element = $compile('
    ')($scope); template = $templateCache.get('to/templates/myTemplate.html'); $scope.$digest(); }));
  4. Finalmente, eu testei dessa maneira. No seu arquivo Spec.js:

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

Para carregar o template html dinamicamente em $ templateCache, você poderia apenas usar o pré-processador de karma html2js, como explicado aqui

isso se resume a adicionar templates ‘ .html’ aos seus arquivos no arquivo conf.js, assim como pré-processadores = {‘ .html’: ‘html2js’};

E use

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

em seu arquivo de teste js

Se você estiver usando o Karma, considere o uso do pré-processador karma-ng-html2js para pré-compilar seus modelos HTML externos e evitar que o Angular tente HTTP para obtê- los durante a execução do teste. Eu lutei com isso por alguns dos nossos – no meu caso, os caminhos parciais do templateUrl foram resolvidos durante a execução normal do aplicativo, mas não durante os testes – devido a diferenças nas estruturas dir do aplicativo vs.

Se você estiver usando o plugin jasmine-maven juntamente com o RequireJS, poderá usar o plug – in de texto para carregar o conteúdo do modelo em uma variável e, em seguida, colocá-lo no cache do modelo.

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

Se você usar requirejs em seus testes, então você pode usar o plugin ‘text’ para puxar o template html e colocá-lo no $ 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(''); scope = $rootScope; $compile(element)(scope); scope.$digest(); })); }); }); 

Eu resolvo esse problema com a compilation de todos os modelos para o templatecache. Eu estou usando gole, você pode encontrar solução semelhante para grunhir também. Meu templateUrls em diretivas, modais parece

 `templateUrl: '/templates/directives/sidebar/tree.html'` 
  1. Adicione um novo pacote npm no meu package.json

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

  2. No arquivo gulp, adicione o templatecache e uma nova tarefa:

    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. Adicione todos os arquivos js em index.html

  4. Apreciar!