คำสั่งการทดสอบหน่วย AngularJS พร้อม templateUrl


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

        });
    });
});

เมื่อฉันรันสิ่งนี้ในข้อผิดพลาดข้อมูลจำเพาะจัสมินของฉันฉันได้รับข้อผิดพลาดต่อไปนี้:

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กำลังตรวจสอบว่ามีการพยายามร้องขอหรือไม่
Alex Osborn

1
อา. ฉันต้องการวิธีบอกให้คน$httpBackendล้อเลียนใช้ URL ที่ให้ไว้ในคำสั่งด้านล่างtemplateUrlและไปรับมัน ฉันคิดว่าpassThroughจะทำแบบนี้ คุณรู้วิธีอื่นในการทำเช่นนี้หรือไม่?
Jared

2
อืมฉันยังไม่ได้ทำการทดสอบ e2e มากนัก แต่ตรวจสอบเอกสาร - คุณได้ลองใช้แบ็กเอนด์ e2e แทนหรือไม่ฉันคิดว่านั่นเป็นสาเหตุที่คุณไม่มีเมธอด passThrough - docs.angularjs.org/api/ngMockE2E.$httpBackend
Alex ออสบอร์น

คำตอบ:


187

คุณถูกต้องที่เกี่ยวข้องกับ ngMock โมดูล ngMock จะโหลดโดยอัตโนมัติสำหรับการทดสอบเชิงมุมทุกครั้งและจะเริ่มต้นการจำลอง$httpBackendเพื่อจัดการกับการใช้$httpบริการใด ๆซึ่งรวมถึงการดึงเทมเพลต ระบบแม่แบบพยายามโหลดแม่แบบผ่าน$httpและกลายเป็น "คำขอที่ไม่คาดคิด" สำหรับผู้จำลอง

สิ่งที่คุณต้องมีวิธีที่จะโหลดก่อนแม่ลงมาเพื่อที่ว่าพวกเขากำลังที่มีอยู่แล้วเมื่อถามเชิงมุมสำหรับพวกเขาโดยไม่ต้องใช้$templateCache$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

สำหรับตัวอย่างที่สมบูรณ์, ดูที่นี้ตัวอย่างที่ยอมรับจากการทดสอบเชิงมุม guru 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);
}));

อย่างจริงจังแม้ว่า ใช้กรรม . ใช้เวลาในการตั้งค่าเล็กน้อย แต่จะช่วยให้คุณสามารถเรียกใช้การทดสอบทั้งหมดของคุณในหลายเบราว์เซอร์พร้อมกันได้จากบรรทัดคำสั่ง ดังนั้นคุณสามารถใช้มันเป็นส่วนหนึ่งของระบบการผสานรวมอย่างต่อเนื่องและ / หรือคุณสามารถตั้งเป็นคีย์ลัดจากตัวแก้ไขของคุณ ดีกว่า alt-tab-refresh-ad-infinitum มาก


6
สิ่งนี้อาจชัดเจน แต่ถ้าคนอื่นติดอยู่ในสิ่งเดียวกันและมองหาคำตอบที่นี่: ฉันไม่สามารถใช้งานได้โดยไม่ต้องเพิ่มpreprocessorsรูปแบบไฟล์ (เช่น"path/to/templates/**/*.html") ในfilesส่วนในkarma.conf.js.
โยฮัน

1
มีประเด็นสำคัญที่ไม่รอคำตอบก่อนดำเนินการต่อหรือไม่? จะอัปเดตค่าเมื่อมีการร้องขอกลับมาหรือไม่ (IE ใช้เวลา 30 วินาที)
Jackie

1
@Jackie ฉันคิดว่าคุณกำลังพูดถึงตัวอย่าง "non-Karma" ที่ฉันใช้falseพารามิเตอร์สำหรับการopenเรียกของ XHR เพื่อทำให้ซิงโครนัส หากคุณไม่ทำเช่นนั้นการดำเนินการจะดำเนินต่อไปอย่างสนุกสนานและเริ่มดำเนินการทดสอบของคุณโดยไม่ต้องโหลดเทมเพลต นั่นทำให้คุณกลับสู่ปัญหาเดิม: 1) คำขอแม่แบบออกไป 2) การทดสอบเริ่มดำเนินการ 3) การทดสอบรวบรวมคำสั่งและแม่แบบยังไม่โหลด 4) Angular ขอเทมเพลตผ่าน$httpบริการซึ่งถูกล้อเลียน 5) $httpบริการจำลองบ่นว่า: "คำขอที่ไม่คาดคิด"
SleepyMurph

1
ฉันสามารถวิ่งฮึดฮัดได้โดยไม่มีกรรม
FlavourScape

5
อีกสิ่งหนึ่งที่คุณจำเป็นต้องติดตั้งกรรม NG-html2js-preprocessor ( npm install --save-dev karma-ng-html2js-preprocessor) และเพิ่มเข้าไปในส่วนปลั๊กอินของคุณkarma.conf.jsตามstackoverflow.com/a/19077966/859631
Vincent

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 นั่นเป็นคำตอบที่ไม่ยั่งยืนและเหตุผลที่เราเลือกใช้เทมเพลต over templateUrl แม้ว่าฉันจะไม่ชอบให้ html ของฉันเป็นสตริงขนาดใหญ่ในคำสั่ง แต่ก็เป็นวิธีแก้ปัญหาที่ยั่งยืนที่สุดในการไม่ต้องอัปเดต html สองแห่ง ซึ่งใช้เวลาในการสร้างภาพไม่มากนักที่ html จะไม่ตรงกันในช่วงเวลาหนึ่ง
Sten Muchow

12

ปัญหาเบื้องต้นนี้สามารถแก้ไขได้โดยเพิ่มสิ่งนี้:

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

นั่นเป็นเพราะมันพยายามหา$ httpBackendในโมดูลngMockตามค่าเริ่มต้นและมันไม่เต็ม


1
นั่นเป็นคำตอบที่ถูกต้องสำหรับคำถามเดิม (นั่นคือคำตอบที่ช่วยฉัน)
จ้า

ลองแล้ว แต่ passThrough () ยังไม่ได้ผลสำหรับฉัน แต่ยังคงให้ข้อผิดพลาด "คำขอที่ไม่คาดคิด"
frodo2975

8

วิธีแก้ปัญหาที่ฉันเข้าถึงต้องการ Jasmine-jquery.js และพร็อกซีเซิร์ฟเวอร์

ฉันทำตามขั้นตอนเหล่านี้:

  1. ในarma.conf:

เพิ่ม jasmine-jquery.js ในไฟล์ของคุณ

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

เพิ่มพร็อกซีเซิร์ฟเวอร์ที่จะเซิร์ฟเวอร์การแข่งขันของคุณ

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. ในสเป็คของคุณ

    อธิบาย ('MySpec', function () {var $ scope, 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 ในการทดสอบของฉัน สถานที่อื่น ๆ ที่แนะนำให้เรียกใช้ $ 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');
  }));
});

ทางออกแรกที่ดีที่ไม่พยายามบังคับให้ dev ใช้ Karma ทำไมคนเชิงมุมถึงทำบางสิ่งที่เลวร้ายและหลีกเลี่ยงได้ง่ายในช่วงกลางของสิ่งที่เจ๋ง ๆ ? pfff
Fabio Milheiro

ฉันเห็นคุณเพิ่ม 'test / mock / ** / *. js' และฉันคิดว่ามันคือการโหลดสิ่งที่ถูกเยาะเย้ยเช่นบริการและทั้งหมด? ฉันกำลังมองหาวิธีที่จะหลีกเลี่ยงการทำซ้ำรหัสของบริการจำลอง คุณช่วยให้ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ไหม
เตฟาน

จำไม่ได้แน่ชัด แต่มีการตั้งค่าที่เป็นไปได้เช่น JSON สำหรับบริการ $ http ไม่มีอะไรแฟนซี
Bartek

มีปัญหานี้วันนี้ - ทางออกที่ดี เราใช้กรรม แต่เราใช้ Chutzpah ด้วย - ไม่มีเหตุผลใดที่เราควรถูกบังคับให้ใช้กรรมและมีเพียงกรรมเท่านั้นที่จะสามารถกำหนดคำสั่งทดสอบได้
lwalden

เรากำลังใช้ Django กับ Angular และมันใช้งานได้ดีในการทดสอบคำสั่งที่โหลด templateUrl ของมันstaticเช่นbeforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); ขอบคุณ!
Aleck Landgraf

6

หากคุณใช้ Grunt คุณสามารถใช้ grunt-angular-template มันโหลดเทมเพลตของคุณใน templateCache และมันจะเปลี่ยนไปตามการกำหนดค่าข้อกำหนดของคุณ

config ตัวอย่างของฉัน:

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สำหรับกรรม ในไฟล์arma.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 ตามที่อธิบายไว้ที่นี่

สิ่งนี้ทำให้เพิ่มเทมเพลต ' .html' ลงในไฟล์ของคุณในไฟล์ conf.js เช่นกัน preprocessors = {' .html': 'html2js'};

และใช้

beforeEach(module('..'));

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

ลงในไฟล์ทดสอบ js ของคุณ


ฉันได้รับUncaught SyntaxError: Unexpected token <
Melbourne2991

2

หากคุณใช้ Karma ให้พิจารณาใช้arma-ng-html2js-preprocessorเพื่อคอมไพล์เทมเพลต HTML ภายนอกของคุณล่วงหน้าและหลีกเลี่ยงไม่ให้ Angular พยายาม HTTP GET ระหว่างการดำเนินการทดสอบ ฉันต่อสู้กับสิ่งนี้กับพวกเราสองคน - ในเส้นทางบางส่วนของ templateUrl ของฉันได้รับการแก้ไขในระหว่างการเรียกใช้งานแอปปกติ แต่ไม่ใช่ในระหว่างการทดสอบเนื่องจากความแตกต่างในแอปเทียบกับโครงสร้าง dir การทดสอบ


2

หากคุณใช้จัสมิน - มาเวน- ปลั๊กอินร่วมกับ 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 ฉันใช้อึกคุณสามารถหาวิธีแก้ปัญหาที่คล้ายกันสำหรับฮึดฮัดได้เช่นกัน templateUrls ของฉันในคำสั่งรูปแบบดูเหมือนว่า

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. เพิ่มแพ็คเกจ npm ใหม่ใน package.json ของฉัน

    "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. เพิ่มไฟล์ 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. สนุก!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.