Единичное тестирование Директива AngularJS с шаблономUrl
У меня есть директива AngularJS, которая имеет templateUrl
. Я пытаюсь unit test с Жасмином.
Мой Jasmine JavaScript выглядит следующим образом, по рекомендации 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');
});
});
});
});
Когда я запускаю это в своей ошибке спецификации Jasmine, я получаю следующую ошибку:
TypeError: Object #<Object> has no method 'passThrough'
Все, что я хочу, это загрузить templateUrl как есть - я не хочу использовать respond
. Я считаю, что это может быть связано с ним, используя ngMock вместо ngMockE2E. Если это преступник, как использовать последний вместо первого?
Спасибо заранее!
Ответы
Ответ 1
То, что я закончил, - это получить кеш шаблона и поместить в него представление. У меня нет контроля над использованием ngMock, получается:
beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
$scope = _$rootScope_;
$compile = _$compile_;
$templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
Ответ 2
Вы правы, что это связано с ngMock. Модуль ngMock автоматически загружается для каждого теста Angular, и он инициализирует mock $httpBackend
для обработки любого использования службы $http
, которая включает выборка шаблонов. Система шаблонов пытается загрузить шаблон через $http
, и он становится "неожиданным запросом" для макета.
Что вам нужно, чтобы предварительно загрузить шаблоны в $templateCache
, чтобы они уже были доступны, когда Angular запрашивает их, не используя $http
.
Предпочтительное решение: карма
Если вы используете 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, шаблоны и тесты.
Решение без кармы
Если вы не используете Карму по какой-либо причине (у меня был негибкий процесс сборки в устаревшем приложении), и я просто тестирую ее в браузере, я обнаружил, что вы можете обойти 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);
}));
Серьезно, правда. Используйте Karma. Для настройки требуется небольшая работа, но она позволяет запускать все ваши тесты в нескольких браузерах одновременно из командной строки. Таким образом, вы можете использовать его как часть вашей системы непрерывной интеграции и/или вы можете сделать ее ярлыком из своего редактора. Гораздо лучше, чем alt-tab-refresh-ad-infinitum.
Ответ 3
Эта начальная проблема может быть решена путем добавления этого:
beforeEach(angular.mock.module('ngMockE2E'));
Это потому, что он пытается найти $httpBackend в модуле ngMock по умолчанию и не заполнен.
Ответ 4
Решение, которое я достиг, требует jasmine-jquery.js и прокси-сервера.
Я выполнил следующие шаги:
добавить jasmine-jquery.js в свои файлы
files = [
JASMINE,
JASMINE_ADAPTER,
...,
jasmine-jquery-1.3.1,
...
]
добавить прокси-сервер, который будет сервер ваших приборов
proxies = {
'/' : 'http://localhost:3502/'
};
-
В вашей спецификации
описать ('MySpec', function() { var $scope, template; jasmine.getFixtures(). fixturesPath = 'public/partials/';//настраиваемый путь, чтобы вы могли обслуживать реальный шаблон, который вы используете в приложении. 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();
})
});
});
-
Запустите сервер в корневом каталоге приложения
python -m SimpleHTTPServer 3502
-
Запустите карму.
Мне потребовалось некоторое время, чтобы понять это, нужно искать много сообщений, я думаю, что документация об этом должна быть более четкой, так как это такая важная проблема.
Ответ 5
Я решил ту же проблему несколько иначе, чем выбранное решение.
-
Сначала я установил и настроил плагин 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/'
}
-
Затем я загрузил модуль, созданный в beforeEach.
В вашем файле Spec.js:
beforeEach(module('myApp', 'to/templates/myTemplate.html'));
-
Затем я использовал $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();
}));
-
Наконец, я протестировал его таким образом.
В вашем файле Spec.js:
describe('element', function() {
it('should contain the template', function() {
expect(element.html()).toMatch(template);
});
});
Ответ 6
Мое решение:
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 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
Если вы используете Grunt, вы можете использовать grunt- angular -templates. Он загружает ваши шаблоны в шаблонеCache, и он непрозрачен для конфигурации ваших спецификаций.
Мой образец конфигурации:
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');
};
Ответ 8
Чтобы динамически загружать шаблон html в $templateCache, вы можете просто использовать предварительный процессор hmml2js karma, как описано здесь
это сводится к добавлению шаблонов .html к вашим файлам в файле conf.js
также
препроцессоры = { '.html': 'html2js'
};
и используйте
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
в файл тестирования js
Ответ 9
если вы используете Карму, рассмотрите возможность использования karma-ng-html2js-preprocessor, чтобы предварительно скомпилировать внешние HTML-шаблоны и избежать Angular попробовать HTTP ПОЛУЧИТЕ их во время выполнения теста. Я боролся с этим для пары наших - в моем случае partialUrl частичные пути, разрешенные во время обычного выполнения приложения, но не во время тестов, - из-за различий в структурах приложений и тестовых dir.
Ответ 10
Если вы используете jasmine-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);
}));
});
});
Ответ 11
Если вы используете 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();
}));
});
});
Ответ 12
Я разрешаю эту проблему с компиляцией всех шаблонов в templatecache.
Я использую gulp, вы можете найти подобное решение для хрюка тоже.
Мои шаблоны в директивах, модалях выглядят как
`templateUrl: '/templates/directives/sidebar/tree.html'`
-
Добавьте новый пакет npm в мой пакет package.json
"gulp-angular-templatecache": "1.*"
-
В файле 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'));
});
-
Добавить все js файлы в index.html
<script src="/assets/js/lib.js"></script>
<script src="/assets/js/app.js"></script>
<script src="/assets/js/templates.js"></script>
-
Наслаждайтесь!