Chỉ thị AngularJS Unit Testing với templateUrl


122

Tôi có một chỉ thị AngularJS đã templateUrlđược xác định. Tôi đang cố gắng kiểm tra đơn vị nó với Jasmine.

JavaScript Jasmine của tôi trông giống như sau, theo đề xuất của cái này :

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

        });
    });
});

Khi tôi chạy điều này trong lỗi thông số kỹ thuật Jasmine của mình, tôi gặp lỗi sau:

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

Tất cả những gì tôi muốn là để templateUrl được tải như cũ - tôi không muốn sử dụng respond. Tôi tin rằng điều này có thể liên quan đến việc sử dụng ngMock thay vì ngMockE2E . Nếu đây là thủ phạm, làm cách nào để sử dụng cái sau thay vì cái trước?

Cảm ơn trước!


1
Tôi đã không sử dụng .passThrough();theo cách đó, nhưng từ các tài liệu, bạn đã thử điều gì đó như: $httpBackend.expectGET('path/to/template.html'); // do action here $httpBackend.flush();Tôi nghĩ điều này phù hợp với cách sử dụng của bạn hơn - bạn không muốn bắt yêu cầu, tức là whenGet(), thay vào đó hãy kiểm tra xem nó đã được gửi chưa, và sau đó thực sự gửi nó?
Alex Osborn

1
Cảm ơn vi đa trả lơi. Tôi không nghĩ rằng điều đó expectGETsẽ gửi yêu cầu ... ít nhất là ra khỏi hộp. Trong tài liệu, ví dụ của họ với /auth.pycó một $httpBackend.whentrước $httpBackend.expectGET$httpBackend.flushcác cuộc gọi.
Jared

2
Đó là chính xác, expectGetchỉ là kiểm tra xem một yêu cầu đã được thử.
Alex Osborn

1
Ah. Tôi cần một cách để nói với người $httpBackendmô phỏng thực sự sử dụng URL được cung cấp trong chỉ thị bên dưới templateUrlvà đi lấy nó. Tôi nghĩ passThroughsẽ làm điều này. Bạn có biết một cách khác để làm điều này?
Jared

2
Rất tiếc, tôi chưa thực hiện nhiều thử nghiệm e2e, nhưng kiểm tra tài liệu - bạn đã thử sử dụng phụ trợ e2e thay thế chưa - Tôi nghĩ đó là lý do tại sao bạn không có phương thức passThrough - docs.angularjs.org/api/ngMockE2E.$httpBackend
Alex Osborn

Câu trả lời:


187

Bạn đúng là nó liên quan đến ngMock. Mô-đun ngMock được tự động tải cho mọi thử nghiệm Angular và nó khởi tạo mô-đun $httpBackendđể xử lý bất kỳ hoạt động sử dụng $httpdịch vụ nào, bao gồm cả việc tìm nạp mẫu. Hệ thống mẫu cố gắng tải mẫu qua $httpvà nó trở thành "yêu cầu không mong muốn" đối với mô hình.

Những gì bạn cần một cách để tải trước các mẫu vào $templateCacheđể chúng đã có sẵn khi Angular yêu cầu mà không cần sử dụng $http.

Giải pháp ưa thích: Karma

Nếu bạn đang sử dụng Karma để chạy thử nghiệm của mình (và bạn nên làm như vậy), bạn có thể định cấu hình nó để tải các mẫu cho bạn bằng bộ xử lý tiền ng-html2js . Ng-html2js đọc các tệp HTML mà bạn chỉ định và chuyển đổi chúng thành một mô-đun Angular để tải trước $templateCache.

Bước 1: Bật và định cấu hình bộ xử lý trước trong 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"
},

Nếu bạn đang sử dụng Yeoman để xây dựng ứng dụng của mình, cấu hình này sẽ hoạt động

plugins: [ 
  'karma-phantomjs-launcher', 
  'karma-jasmine', 
  'karma-ng-html2js-preprocessor' 
], 

preprocessors: { 
  'app/views/*.html': ['ng-html2js'] 
}, 

ngHtml2JsPreprocessor: { 
  stripPrefix: 'app/', 
  moduleName: 'my.templates' 
},

Bước 2: Sử dụng mô-đun trong các bài kiểm tra của bạn

// my-test.js

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

Để có một ví dụ hoàn chỉnh, hãy xem ví dụ chính tắc này từ Vojta Jina, guru kiểm tra Angular . Nó bao gồm toàn bộ thiết lập: cấu hình nghiệp, mẫu và thử nghiệm.

Một Giải pháp Không Nghiệp

Nếu bạn không sử dụng Karma vì bất kỳ lý do gì (tôi đã có một quy trình xây dựng không linh hoạt trong ứng dụng cũ) và chỉ đang thử nghiệm trong một trình duyệt, tôi nhận thấy rằng bạn có thể vượt qua sự tiếp quản của ngMock $httpBackendbằng cách sử dụng một XHR thô để tìm nạp mẫu thực và chèn nó vào $templateCache. Giải pháp này kém linh hoạt hơn nhiều, nhưng nó sẽ hoàn thành công việc ngay bây giờ.

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

Nghiêm túc đấy. Sử dụng Karma . Phải mất một chút công việc để thiết lập, nhưng nó cho phép bạn chạy tất cả các thử nghiệm của mình, trong nhiều trình duyệt cùng một lúc, từ dòng lệnh. Vì vậy, bạn có thể có nó như một phần của hệ thống tích hợp liên tục của mình và / hoặc bạn có thể đặt nó thành một phím tắt từ trình soạn thảo của mình. Tốt hơn nhiều so với alt-tab-refresh-ad-infinitum.


6
Điều này có thể rõ ràng, nhưng nếu những người khác gặp khó khăn về điều tương tự và hãy tìm câu trả lời ở đây: Tôi không thể làm cho nó hoạt động mà không thêm preprocessorsmẫu tệp (ví dụ "path/to/templates/**/*.html") vào filesphần trong karma.conf.js.
Johan

1
Vì vậy, có bất kỳ vấn đề lớn nào với việc không đợi phản hồi trước khi tiếp tục? Nó sẽ chỉ cập nhật giá trị khi yêu cầu quay lại (IE mất 30 giây)?
Jackie

1
@Jackie Tôi giả sử bạn đang nói về ví dụ "non-Karma" trong đó tôi sử dụng falsetham số cho openlệnh gọi của XHR để làm cho nó đồng bộ. Nếu bạn không làm điều đó, quá trình thực thi sẽ tiếp tục và bắt đầu thực hiện các thử nghiệm của bạn mà không cần tải mẫu. Điều đó giúp bạn quay trở lại cùng một vấn đề: 1) Yêu cầu về mẫu biến mất. 2) Kiểm tra bắt đầu thực hiện. 3) Kiểm tra biên dịch một chỉ thị, và mẫu vẫn chưa được tải. 4) Angular yêu cầu mẫu thông qua $httpdịch vụ của nó , mẫu này đã bị chế nhạo. 5) $httpDịch vụ giả than phiền: "yêu cầu đột xuất".
SleepyMurph,

1
Tôi đã có thể chạy grunt-jasmine mà không có Karma.
FlavorScape

5
Một điều khác: bạn cần cài đặt karma-ng-html2js-preprocessor ( npm install --save-dev karma-ng-html2js-preprocessor) và thêm nó vào phần plugin của bạn karma.conf.js, theo stackoverflow.com/a/19077966/859631 .
Vincent

37

Cuối cùng những gì tôi làm là lấy bộ đệm ẩn mẫu và đưa chế độ xem vào đó. Tôi không có quyền kiểm soát việc không sử dụng ngMock, hóa ra là:

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

26
Đây là khiếu nại của tôi với phương pháp này ... Bây giờ nếu chúng ta sẽ có một đoạn html lớn mà chúng ta sẽ chèn dưới dạng một chuỗi vào bộ đệm ẩn mẫu thì chúng ta sẽ làm gì khi chúng ta thay đổi html trên giao diện người dùng ? Thay đổi html trong thử nghiệm là tốt? IMO đó là một câu trả lời không bền vững và lý do chúng tôi sử dụng mẫu thay vì tùy chọn templateUrl. Mặc dù tôi rất không thích có html của mình dưới dạng một chuỗi lớn trong chỉ thị - đó là giải pháp bền vững nhất để không phải cập nhật hai nơi html. Mà không cần nhiều hình ảnh mà html theo thời gian có thể không khớp.
Sten Muchow

12

Vấn đề ban đầu này có thể được giải quyết bằng cách thêm vào:

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

Đó là bởi vì nó cố gắng tìm $ httpBackend trong mô-đun ngMock theo mặc định và nó không đầy.


1
Đó thực sự là câu trả lời chính xác cho câu hỏi ban đầu (đó là câu đã giúp tôi).
Mat

Đã thử điều này, nhưng passThrough () vẫn không hoạt động với tôi. Nó vẫn xuất hiện lỗi "Yêu cầu không mong muốn".
frodo2975,

8

Giải pháp mà tôi đạt được cần jasmine-jquery.js và một máy chủ proxy.

Tôi đã làm theo các bước sau:

  1. Trong karma.conf:

thêm jasmine-jquery.js vào tệp của bạn

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

thêm một máy chủ proxy sẽ phục vụ đồ đạc của bạn

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. Trong thông số của bạn

    description ('MySpec', function () {var $ scope, template; jasmine.getFixtures (). fixturesPath = 'public / partials /'; // đường dẫn tùy chỉnh để bạn có thể cung cấp mẫu thực mà bạn sử dụng trên ứng dụng trước đâyEach (function () {template = angle.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. Chạy một máy chủ trên thư mục gốc của ứng dụng của bạn

    python -m SimpleHTTPServer 3502

  3. Khởi nghiệp.

Tôi đã mất một lúc để tìm ra điều này, phải tìm kiếm nhiều bài viết, tôi nghĩ rằng tài liệu về điều này nên rõ ràng hơn, vì nó là một vấn đề quan trọng.


Tôi đã gặp sự cố khi cung cấp nội dung từ localhost/base/specsvà thêm máy chủ proxy khi python -m SimpleHTTPServer 3502chạy đã khắc phục sự cố. Ngài là một thiên tài!
pbojinov,

Tôi nhận được một phần tử trống được trả về từ $ compile trong các thử nghiệm của mình. Các địa điểm khác được đề xuất chạy $ scope. $ Digan (): vẫn trống. Chạy $ scope. $ Apply () vẫn hoạt động. Tôi nghĩ đó là do tôi đang sử dụng một bộ điều khiển trong chỉ thị của mình? Không chắc. Cảm ơn vì lời khuyên! Đã giúp!
Sam Simmons

7

Giải pháp của tôi:

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

các bài kiểm tra:

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

Giải pháp tốt đầu tiên không cố ép các nhà phát triển sử dụng Karma. Tại sao những chàng trai góc cạnh lại làm điều gì đó tồi tệ và dễ dàng tránh được ở giữa một thứ tuyệt vời như vậy? pfff
Fabio Milheiro

Tôi thấy bạn thêm một 'test / mock / ** / *. Js' và tôi cho rằng nó là để tải tất cả những thứ bị chế nhạo như dịch vụ và tất cả? Tôi đang tìm cách để tránh trùng lặp mã của các dịch vụ giả mạo. Bạn có thể cho chúng tôi biết thêm một chút về điều đó không?
Stephane

không nhớ chính xác, nhưng có thể có các cài đặt, ví dụ như JSON cho dịch vụ $ http. Không có gì lạ mắt.
bartek

Đã có vấn đề này ngày hôm nay - giải pháp tuyệt vời. Chúng ta sử dụng nghiệp nhưng chúng ta cũng sử dụng Chutzpah - không có lý do gì chúng ta bị buộc phải sử dụng nghiệp và chỉ nghiệp mới có thể thực hiện các chỉ thị kiểm tra đơn vị.
lwalden

Chúng tôi đang sử dụng Django với Angular và điều này hoạt động giống như một sự quyến rũ để kiểm tra một chỉ thị tải templateUrl của nó static, ví dụ: beforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); Cảm ơn!
Aleck Landgraf

6

Nếu bạn đang sử dụng Grunt, bạn có thể sử dụng các mẫu grunt-angle. Nó tải các mẫu của bạn trong templateCache và nó hiển thị rõ ràng với cấu hình thông số kỹ thuật của bạn.

Cấu hình mẫu của tôi:

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

Tôi đã giải quyết vấn đề tương tự theo một cách hơi khác so với giải pháp đã chọn.

  1. Đầu tiên, tôi đã cài đặt và định cấu hình plugin ng-html2js cho karma. Trong tệp 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. Sau đó, tôi tải mô-đun đã tạo trong beforeEach. Trong tệp Spec.js của bạn:

    beforeEach(module('myApp', 'to/templates/myTemplate.html'));
  3. Sau đó, tôi sử dụng $ templateCache.get để lưu trữ nó vào một biến. Trong tệp Spec.js của bạn:

    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. Cuối cùng, tôi đã thử nghiệm nó theo cách này. Trong tệp Spec.js của bạn:

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

4

Để tải động mẫu html vào $ templateCache, bạn chỉ có thể sử dụng bộ xử lý trước html2js karma, như được giải thích ở đây

điều này kết thúc với việc thêm các mẫu ' .html' vào các tệp của bạn trong tệp conf.js cũng như các bộ tiền xử lý = {' .html': 'html2js'};

Và sử dụng

beforeEach(module('..'));

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

vào tệp thử nghiệm js của bạn


Tôi đang nhận đượcUncaught SyntaxError: Unexpected token <
Melbourne2991

2

nếu bạn đang sử dụng Karma, hãy cân nhắc sử dụng bộ tiền xử lý karma-ng-html2js- để biên dịch trước các mẫu HTML bên ngoài của bạn và tránh để Angular cố gắng HTTP GET chúng trong quá trình thực thi thử nghiệm. Tôi đã đấu tranh với điều này cho một số người của chúng tôi - trong trường hợp của tôi, các đường dẫn một phần của templateUrl được giải quyết trong quá trình thực thi ứng dụng bình thường nhưng không phải trong quá trình thử nghiệm - do sự khác biệt trong cấu trúc dir thử nghiệm so với ứng dụng.


2

Nếu bạn đang sử dụng jasmine-maven-plugin cùng với RequestJS, bạn có thể sử dụng plugin văn bản để tải nội dung mẫu vào một biến và sau đó đặt nó vào bộ đệm ẩn mẫu.


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

    });
});

Bạn có thể làm điều này mà không có Karma?
Winnemucca

2

Nếu bạn sử dụng requestjs trong các thử nghiệm của mình thì bạn có thể sử dụng plugin 'văn bản' để kéo mẫu html vào và đặt nó vào $ 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

Tôi giải quyết vấn đề này bằng cách biên dịch tất cả các mẫu thành templatecache. Tôi đang sử dụng gulp, bạn cũng có thể tìm thấy giải pháp tương tự cho grunt. TemplateUrl của tôi trong chỉ thị, phương thức trông giống như

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. Thêm một gói npm mới trong package.json của tôi

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

  2. Trong tệp gulp, thêm templatecache và một tác vụ mới:

    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. Thêm tất cả các tệp js trong index.html

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

  4. Thưởng thức!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.