使用templateUrl进行单元测试AngularJS指令


122

我有一个已templateUrl定义的AngularJS指令。我正在尝试与Jasmine进行单元测试。

根据此建议,我的Jasmine 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 spec错误中运行此命令时,出现以下错误:

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

我想要的只是让templateUrl原样加载-我不想使用respond。我相信这可能与使用ngMock而不是ngMockE2E有关。如果这是罪魁祸首,如何使用后者而不是前者?

提前致谢!


1
我没有用过.passThrough();这种方式,但是从文档中,您是否尝试过类似的方法:$httpBackend.expectGET('path/to/template.html'); // do action here $httpBackend.flush();我认为这更适合您的用法-您不想捕获请求,即whenGet(),而是检查请求是否已发送,然后实际发送?
Alex Osborn

1
谢谢回复。我不认为expectGET发送请求……至少是开箱即用的。在文档它们与例如/auth.py$httpBackend.when$httpBackend.expectGET$httpBackend.flush调用。
杰瑞德(Jared)

2
没错,expectGet只是检查是否尝试了请求。
亚历克斯·奥斯本

1
啊。好吧,我需要一种方法告诉$httpBackend模拟程序实际使用伪指令下提供的URL templateUrl并获取它。我以为passThrough会这样做。您知道其他方法吗?
杰瑞德(Jared)

2
嗯,我还没有做很多e2e测试,但是检查了文档-您是否尝试过使用e2e后端-我认为这就是为什么您没有方法passThrough-docs.angularjs.org/api/ngMockE2E.$httpBackend
Alex奥斯本2013年

Answers:


187

您是正确的,它与ngMock有关。每次Angular测试都会自动加载ngMock模块,它会初始化模拟程序$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(我在旧版应用程序中没有灵活的构建过程),而只是在浏览器中进行测试,那么我发现您可以$httpBackend通过使用原始XHR来获取ngMock的实际模板来绕过它的接管并将其插入$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
2014年

1
那么,是否有重大问题导致在继​​续之前不等待响应?当请求返回时(IE花费30秒),它是否只会更新值?
2014年

1
@Jackie我假设您正在谈论“ non-Karma”示例,在该示例中,我使用falseXHR open调用的参数来使其同步。如果您不这样做,执行过程将轻松继续并开始执行测试,而无需加载模板。这使您又回到了相同的问题:1)请求模板失败。2)测试开始执行。3)测试会编译一条指令,并且模板仍未加载。4)Angular通过其$http服务请求模板,该服务已被模拟。5)模拟$http服务抱怨:“意外请求”。
SleepyMurph

1
在没有因果报应的情况下,我可以运行茉莉花香。
FlavorScape 2014年

5
另一件事:根据stackoverflow.com/a/19077966/859631,您需要安装karma-ng-html2js-preprocessor(npm install --save-dev karma-ng-html2js-preprocessor),并将其添加到您的插件部分。karma.conf.js
文森特

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?IMO这是不可持续的答案,也是我们选择使用template over templateUrl选项的原因。即使我非常不喜欢将我的html作为大量字符串包含在指令中-这是不必更新html的两个位置的最可持续的解决方案。随着时间的推移,这并不需要太多的影像,HTML可能无法匹配。
Sten Muchow 2014年

12

可以通过添加以下内容来解决此初始问题:

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

这是因为默认情况下它会尝试在ngMock模块中找到$ httpBackend,并且它不完整。


1
嗯,这确实是对原始问题的正确答案(这对我有帮助)。
2014年

试过这个,但是passThrough()仍然对我不起作用。它仍然给“意外的请求”错误。
frodo2975

8

我达到的解决方案需要jasmine-jquery.js和代理服务器。

我遵循以下步骤:

  1. 在karma.conf中:

将jasmine-jquery.js添加到您的文件

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

添加一个代理服务器,它将为您的灯具提供服务

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. 根据您的规格

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

    });

  2. 在您应用的根目录上运行服务器

    python -m SimpleHTTPServer 3502

  3. 运行业力。

我花了一段时间才找出来,不得不搜索许多帖子,我认为关于此的文档应该更加清晰,因为这是一个重要的问题。


我在localhost/base/specs通过python -m SimpleHTTPServer 3502运行固定资产来增加资产和添加代理服务器方面遇到了麻烦。先生,你真是个天才!
pbojinov

在测试中,我从$ compile返回了一个空元素。建议其他地方运行$ scope。$ digest():仍然为空。运行$ scope。$ apply()可以。我认为是因为我在指令中使用了控制器?不确定。谢谢你的建议!帮忙!
Sam Simmons

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 2014年

我看到您添加了一个'test / mock / ** / *。js',我想它是要加载所有模拟的东西,例如服务和所有?我正在寻找避免模拟服务代码重复的方法。您能再给我们看看更多吗?
Stephane 2015年

记不清了,但是可能有一些设置,例如$ http服务的JSON。没有什么花哨。
bartek

今天有这个问题-很好的解决方案。我们使用业力,但也使用Chutzpah-没有理由我们应该被迫使用业力,而只有业力才能够进行单元测试指令。
lwalden 2015年

我们将Django与Angular结合使用,这就像一种魅力,可以通过它来测试加载其templateUrl的指令static,例如,beforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); 谢谢!
Aleck Landgraf 2015年

6

如果使用的是Grunt,则可以使用grunt-angular-templates。它将您的模板加载到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因缘预处理器,如解释在这里

这归结为在conf.js文件中的文件中添加模板' .html'以及preprocessors = {' .html':'html2js'};

和使用

beforeEach(module('..'));

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

进入您的js测试文件


我正在Uncaught SyntaxError: Unexpected token <
Melbourne2991

2

如果您使用的是Karma,请考虑使用karma-ng-html2js-preprocessor预编译您的外部HTML模板,并避免Angular在测试执行期间尝试HTTP GET它们。我为我们中的一些人而苦苦挣扎-在我的情况下,由于应用与测试目录结构的差异,templateUrl的部分路径在正常的应用执行过程中解决了,但在测试过程中却没有解决。


2

如果您将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);
        }));

    });
});

没有业力,您能做到吗?
Winnemucca

2

如果您在测试中使用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();
    }));
  });
});

0

我通过将所有模板编译为templatecache解决此问题。我使用的是gulp,您也可以找到类似的解决方案。我的templateUrls指令,模态如下

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. 在我的package.json中添加一个新的npm包

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

  2. 在gulp文件中添加templatecache和一个新任务:

    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. 在index.html中添加所有js文件

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

  4. 请享用!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.