Директива огляду AngularJS модуля з шаблономUrl


122

У мене є директива AngularJS, яка має templateUrlвизначене. Я намагаюсь перевірити це на Жасмін.

Мій Жасмін 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, я отримую таку помилку:

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

Все, що я хочу, - це те, щоб шаблонUrl був завантажений таким, який є - я не хочу використовувати respond. Я вважаю, що це може бути пов’язано з ним, використовуючи ngMock замість ngMockE2E . Якщо це винуватець, як я можу використовувати останнє замість першого?

Спасибі заздалегідь!


1
Я не використовував .passThrough();це таким чином, але чи ви спробували щось із документів: $httpBackend.expectGET('path/to/template.html'); // do action here $httpBackend.flush();я вважаю, що це краще відповідає вашому використанню - ви не хочете запитувати запит whenGet(), а натомість перевірити, чи надсилається він, а потім насправді відправити?
Алекс Осборн

1
Дякую за відповідь. Я не думаю, що це expectGETнадсилає запити ... принаймні, поза коробкою. У документах їхній приклад із /auth.py" $httpBackend.whenперед" $httpBackend.expectGETта " $httpBackend.flushдзвінками".
Джаред

2
Це правильно, expectGetлише перевіряється, чи було здійснено спробу.
Алекс Осборн

1
Ага. Ну, мені потрібен спосіб сказати $httpBackendмакету, щоб насправді використовувати URL, вказаний в директиві, templateUrlі перейти до нього. Я думав, passThroughщо зроблю це. Ви знаєте інший спосіб зробити це?
Джаред

2
Хм, я ще не робив багато тестів на e2e, але перевіряючи документи - чи намагалися ви замість цього використати бекенд e2e - я думаю, саме тому у вас немає методу passThrough - docs.angularjs.org/api/ngMockE2E.$httpBackend
Alex Осборн

Відповіді:


187

Ви правильні, що це пов'язано з ngMock. Модуль ngMock автоматично завантажується для кожного тесту Angular, і він ініціалізує макет $httpBackendдля обробки будь-якого використання $httpсервісу, що включає отримання шаблону. Система шаблонів намагається завантажити шаблон наскрізь, $httpі він стає "несподіваним запитом" до макету.

Що вам потрібно, щоб попередньо завантажити шаблони, $templateCacheщоб вони вже були доступні, коли Angular запитує їх, не використовуючи $http.

Краще рішення: Карма

Якщо ви використовуєте Karma для запуску своїх тестів (і вам слід), ви можете налаштувати її для завантаження шаблонів для вас за допомогою препроцесора ng-html2js . Ng-html2js зчитує вказані вами HTML-файли та перетворює їх у кутовий модуль, який попередньо завантажує $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

Для повного прикладу подивіться на цей канонічний приклад з кутового тесту Гуру Войта Джина . Вона включає в себе цілу настройку: конфігурацію карми, шаблони та тести.

Рішення без карми

Якщо ви з будь-якої причини не використовуєте Кару (у мене був нестабільний процес збирання у застарілому додатку) і ви просто тестуєте в браузері, я виявив, що ви можете обійти захоплення ngMock, $httpBackendвикористовуючи необроблений XHR для отримання шаблону для реального і вставити його в $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);
}));

Однак серйозно. Використовуйте карму . Налаштування потребує небагато роботи, але це дозволяє запускати всі тести в декількох браузерах одночасно з командного рядка. Таким чином, ви можете мати його як частину вашої системи безперервної інтеграції та / або ви можете зробити це ярликом у вашому редакторі. Набагато краще, ніж alt-tab-refresh-ad-infinitum.


6
Це може бути очевидним, але якщо інші застрягають на тому самому і шукають відповіді тут: я не зміг би змусити його працювати, не додавши також preprocessorsшаблон файлу (наприклад "path/to/templates/**/*.html") до filesрозділу в karma.conf.js.
Йоган

1
Чи є якісь основні проблеми, коли не чекати відповіді, перш ніж продовжувати? Чи просто оновить значення, коли запит повернеться (IE займає 30 секунд)?
Джекі

1
@Jackie Я припускаю, що ви говорите про приклад "не-карми", де я використовую falseпараметр для openвиклику XHR, щоб зробити його синхронним. Якщо цього не зробити, виконання буде весело продовжено і почне виконувати ваші тести, не завантажуючи шаблон. Це повертається право до тієї ж проблеми: 1) Запит на шаблон виходить. 2) Тест починається виконувати. 3) Тест складає директиву, і шаблон все ще не завантажується. 4) Кутовий запитує шаблон через свою $httpслужбу, яка знущається. 5) Служба макетів $httpскаржиться на "несподіваний запит".
SleepyMurph

1
Мені вдалося запустити грунт-жасмін без Карми.
FlavorScape

5
Інша річ: вам потрібно встановити karma-ng-html2js-препроцесор ( npm install --save-dev karma-ng-html2js-preprocessor) і додати його до розділу плагінів вашого karma.conf.js, відповідно до stackoverflow.com/a/19077966/859631 .
Вінсент

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 також у тесті? ІМО, що є нестійкою відповіддю, і причина, по якій ми пішли з використанням шаблону над параметром templateUrl. Незважаючи на те, що мені дуже не подобається, що мій html є масивним рядком у директиві - це найбільш стійке рішення не оновлювати два місця html. Що не займає багато зображень, що HTML може з часом не збігатися.
Стен Мухов

12

Цю початкову проблему можна вирішити, додавши це:

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

Це тому, що він намагається знайти $ httpBackend у модулі ngMock за замовчуванням, і він не повний.


1
Ну це справді правильна відповідь на початкове запитання (саме це мені допомогло).
Мат

Спробував це, але passThrough () все ще не працював для мене. Він все ще дав помилку "Несподіваний запит".
frodo2975

8

Для мене потрібне рішення Jasmine-jquery.js і проксі-сервер.

Я дотримувався цих кроків:

  1. У karma.conf:

додайте жасмин-jquery.js у свої файли

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

додайте проксі-сервер, який буде обслуговувати ваші світильники

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. У вашій специфікації

    description ('MySpec', function () {var $ obseg, template; jasmine.getFixtures (). fixturesPath = 'public / partials /'; // спеціальний шлях, щоб ви могли обслуговувати реальний шаблон, який ви використовуєте в додатку beforeEach (функція () {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. Запустіть сервер у кореневому каталозі програми

    python -m SimpleHTTPServer 3502

  3. Запустити карму.

Щоб розібратися в цьому, знадобилося певний час, я потребував пошуку багатьох постів, я вважаю, що документація щодо цього повинна бути чіткішою, оскільки це така важлива проблема.


У мене виникли проблеми із обслуговуванням активів localhost/base/specsі додаванням проксі-сервера із python -m SimpleHTTPServer 3502запущеним виправленням. Ви, сер, геній!
pbojinov

У моїх тестах я отримував порожній елемент, повернутий з $ compile. Інші місця пропонують запустити $ range. $ Digest (): все ще порожньо. Запуск $ range. $ Apply () працював, хоча. Я думаю, це було тому, що я використовую контролер у своїй директиві? Не впевнений. Дякую за пораду! Допомогли!
Сем Сіммонс

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

Перше гідне рішення, яке не намагається змусити розробників використовувати Кару. Чому б кутові хлопці робили щось таке погане і легко уникнене посеред чогось такого крутого? pfff
Fabio Milheiro

Я бачу, що ви додаєте "test / mock / ** / *. Js", і я гадаю, що це завантажувати всі знущаються речі, такі як служби та все? Я шукаю способи уникнути дублювання коду знущаються служб. Чи покажете нам трохи більше про це?
Стефан

точно не пам’ятаю, але були ймовірно налаштування, наприклад, JSON для служби $ http. Нічого фантазійного.
bartek

Чи була ця проблема сьогодні - чудове рішення. Ми використовуємо карму, але ми також використовуємо Чуцпа - немає причин, щоб нас змушували використовувати карму і лише карму, щоб мати змогу формувати директиви тестування.
lwalden

Ми використовуємо Django з Angular, і це спрацювало як шарм для перевірки директиви, яка завантажує його templateUrl, хоча static, наприклад, beforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); спасибі!
Алек Ландграф

6

Якщо ви використовуєте Grunt, ви можете використовувати шаблони grunt-angular-шаблони. Він завантажує ваші шаблони в 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 karma, як пояснено тут

це зводиться до додавання шаблонів ' .html' до ваших файлів у файл conf.js, а також препроцесорів = {' .html': 'html2js'};

і використовувати

beforeEach(module('..'));

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

у ваш тестовий файл js


Я отримуюUncaught SyntaxError: Unexpected token <
Мельбурн2991

2

якщо ви використовуєте Karma, подумайте про використання karma-ng-html2js-препроцесора для попередньої компіляції зовнішніх шаблонів HTML і уникайте кутових спроб HTTP GET їх під час виконання тесту. Я боровся з цим для декількох наших - у моєму випадку часткові шляхи templateUrl вирішені під час звичайного виконання додатків, але не під час тестів - через відмінності в структурі додатків проти тестових режимів.


2

Якщо ви використовуєте плагін жасмину-maven разом з 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);
        }));

    });
});

Чи можете ви це зробити без Карми?
Віннемукка

2

Якщо ви використовуєте Requjs у своїх тестах, ви можете використовувати плагін '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

Я вирішую цю проблему, зібравши всі шаблони до шаблону шаблонів. Я використовую gulp, ви можете знайти подібне рішення і для грунту. Мій шаблонUrls в директивах, модалях виглядає так

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. Додайте новий пакет npm у мій package.json

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

  2. У файл gulp додайте шаблон кеша і нове завдання:

    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. Додайте всі js-файли в index.html

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

  4. Насолоджуйтесь!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.