122

使用 AngularJS。

有一个指令。

指令定义templateUrl.

指令需要单元测试。

目前使用 Jasmine 进行单元测试。

推荐如下代码:

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 的使用。

4

12 回答 12

186

你是对的,它与 ngMock 有关。ngMock 模块会为每个 Angular 测试自动加载,它会初始化 mock$httpBackend以处理对$http服务的任何使用,包括模板获取。模板系统尝试通过加载模板$http,它成为模拟的“意外请求”。

您需要一种将模板预加载$templateCache$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 好得多。

于 2013-08-21T21:48:17.420 回答
37

我最终做的是获取模板缓存并将视图放在那里。我无法控制不使用 ngMock,事实证明:

beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
    $scope = _$rootScope_;
    $compile = _$compile_;
    $templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
于 2013-03-05T18:32:23.237 回答
13

这个初始问题可以通过添加这个来解决:

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

那是因为它默认尝试在ngMock模块中查找$httpBackend并且它没有满。

于 2013-05-22T09:46:12.550 回答
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 () { 模板 = 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. 运行业力。

我花了一段时间才弄清楚这一点,不得不搜索很多帖子,我认为关于这个的文档应该更清楚,因为这是一个非常重要的问题。

于 2013-05-13T18:46:04.560 回答
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');
  }));
});
于 2014-09-04T11:06:02.367 回答
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');

};
于 2013-06-12T19:26:26.793 回答
6

我以与所选解决方案略有不同的方式解决了相同的问题。

  1. 首先,我为 karma 安装并配置了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);
      });
    });
    
于 2014-01-21T16:06:42.680 回答
4

要将模板 html 动态加载到 $templateCache 中,您可以使用 html2js 业力预处理器,如此处所述

这归结为将模板“ .html”添加到 conf.js 文件中的文件以及预处理器 = { ' .html': 'html2js' };

并使用

beforeEach(module('..'));

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

进入你的 js 测试文件

于 2013-05-10T17:49:23.073 回答
2

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

于 2014-01-29T18:09:33.667 回答
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);
        }));

    });
});
于 2014-05-15T11:11:04.837 回答
2

如果您在测试中使用 requirejs,那么您可以使用“文本”插件来拉入 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();
    }));
  });
});
于 2015-04-03T18:09:35.367 回答
0

我通过将所有模板编译到模板缓存来解决此问题。我正在使用 gulp,你也可以为 grunt 找到类似的解决方案。我在指令中的 templateUrls,模态看起来像

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

    "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. 在 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. 享受!

于 2015-08-26T08:28:17.940 回答