Unit Testing direktif AngularJS dengan templateUrl

122

Saya memiliki arahan AngularJS yang memiliki templateUrldefinisi. Saya mencoba untuk mengujinya dengan Jasmine.

JavaScript Jasmine saya terlihat seperti berikut, sesuai rekomendasi ini :

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

        });
    });
});

Ketika saya menjalankan ini dalam kesalahan spesifikasi Jasmine saya, saya mendapatkan kesalahan berikut:

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

Yang saya inginkan adalah templateUrl dimuat apa adanya - saya tidak ingin menggunakan respond. Saya yakin ini mungkin terkait dengan penggunaan ngMock daripada ngMockE2E . Jika ini pelakunya, bagaimana cara menggunakan yang terakhir daripada yang pertama?

Terima kasih sebelumnya!

Jared
sumber
1
Saya belum pernah menggunakan .passThrough();dengan cara itu, tetapi dari dokumen, apakah Anda sudah mencoba sesuatu seperti: $httpBackend.expectGET('path/to/template.html'); // do action here $httpBackend.flush();Saya rasa ini lebih cocok dengan penggunaan Anda - Anda tidak ingin menangkap permintaan, yaitu whenGet(), tetapi memeriksa apakah sudah terkirim, dan kemudian sebenarnya Kirimkan?
Alex Osborn
1
Terima kasih balasannya. Saya tidak berpikir itu expectGETmengirimkan permintaan ... setidaknya di luar kotak. Di dokumen, contoh mereka dengan /auth.pymemiliki $httpBackend.whensebelum $httpBackend.expectGETdan $httpBackend.flushpanggilan.
Jared
2
Benar, expectGethanya memeriksa apakah suatu permintaan telah dicoba.
Alex Osborn
1
Ah. Saya perlu cara untuk memberi tahu $httpBackendtiruan untuk benar-benar menggunakan URL yang disediakan di arahan di bawah templateUrldan mendapatkannya. Saya pikir passThroughakan melakukan ini. Apakah Anda mengetahui cara lain untuk melakukan ini?
Jared
2
Hmm, saya belum melakukan banyak pengujian e2e, tetapi memeriksa dokumen - apakah Anda sudah mencoba menggunakan backend e2e sebagai gantinya - saya pikir itu sebabnya Anda tidak mendapat metode passThrough - docs.angularjs.org/api/ngMockE2E.$httpBackend
Alex Osborn

Jawaban:

187

Anda benar bahwa ini terkait dengan ngMock. Modul ngMock dimuat secara otomatis untuk setiap pengujian Angular, dan menginisialisasi tiruan $httpBackenduntuk menangani setiap penggunaan $httplayanan, yang mencakup pengambilan template. Sistem template mencoba untuk memuat template $httpdan itu menjadi "permintaan tak terduga" untuk tiruan.

Apa yang Anda perlukan cara untuk memuat template ke dalam $templateCachesehingga sudah tersedia saat Angular memintanya, tanpa menggunakan $http.

Solusi Pilihan: Karma

Jika Anda menggunakan Karma untuk menjalankan pengujian Anda (dan memang seharusnya demikian), Anda dapat mengkonfigurasinya untuk memuat template untuk Anda dengan preprocessor ng-html2js . Ng-html2js membaca file HTML yang Anda tentukan dan mengubahnya menjadi modul Angular yang memuat file $templateCache.

Langkah 1: Aktifkan dan konfigurasikan preprocessor di file 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"
},

Jika Anda menggunakan Yeoman untuk membuat perancah aplikasi Anda, konfigurasi ini akan berfungsi

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

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

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

Langkah 2: Gunakan modul dalam pengujian Anda

// my-test.js

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

Untuk contoh lengkap, lihat contoh kanonik ini dari guru tes Angular Vojta Jina . Ini mencakup seluruh pengaturan: konfigurasi karma, templat, dan tes.

Solusi Non-Karma

Jika Anda tidak menggunakan Karma karena alasan apa pun (saya memiliki proses pembuatan yang tidak fleksibel di aplikasi lama) dan hanya menguji di browser, saya telah menemukan bahwa Anda dapat mengatasi pengambilalihan ngMock $httpBackenddengan menggunakan XHR mentah untuk mengambil template secara nyata. dan masukkan ke dalam $templateCache. Solusi ini kurang fleksibel, tetapi menyelesaikan pekerjaan untuk saat ini.

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

Serius, sih. Gunakan Karma . Dibutuhkan sedikit kerja untuk menyiapkan, tetapi ini memungkinkan Anda menjalankan semua pengujian Anda, di beberapa browser sekaligus, dari baris perintah. Jadi Anda dapat memilikinya sebagai bagian dari sistem integrasi berkelanjutan Anda, dan / atau Anda dapat menjadikannya tombol pintas dari editor Anda. Jauh lebih baik daripada alt-tab-refresh-ad-infinitum.

SleepyMurph
sumber
6
Ini mungkin jelas, tetapi jika orang lain terjebak pada hal yang sama dan mencari jawabannya di sini: Saya tidak bisa membuatnya bekerja tanpa juga menambahkan preprocessorspola file (misalnya "path/to/templates/**/*.html") ke filesbagian dalam karma.conf.js.
Johan
1
Jadi, apakah ada masalah besar dengan tidak menunggu tanggapan sebelum melanjutkan? Apakah itu hanya memperbarui nilai ketika permintaan kembali (IE membutuhkan waktu 30 detik)?
Jackie
1
@ Jackie Saya berasumsi bahwa Anda sedang berbicara tentang contoh "non-Karma" di mana saya menggunakan falseparameter untuk openpanggilan XHR untuk membuatnya sinkron. Jika Anda tidak melakukannya, eksekusi akan terus berlanjut dan mulai menjalankan pengujian Anda, tanpa memuat template. Itu membuat hak Anda kembali ke masalah yang sama: 1) Permintaan template keluar. 2) Tes mulai dijalankan. 3) Tes mengkompilasi direktif, dan template masih belum dimuat. 4) Angular meminta template melalui $httplayanannya, yang diejek. 5) Layanan tiruan $httpmengeluh: "permintaan tak terduga".
SleepyMurph
1
Saya bisa berlari melati tanpa Karma.
FlavorScape
5
Hal lain: Anda perlu menginstal karma-ng-html2js-preprocessor ( npm install --save-dev karma-ng-html2js-preprocessor), dan menambahkannya ke bagian plugin Anda karma.conf.js, menurut stackoverflow.com/a/19077966/859631 .
Vincent
37

Apa yang akhirnya saya lakukan adalah mendapatkan cache template dan meletakkan tampilan di sana. Saya tidak memiliki kendali atas tidak menggunakan ngMock, ternyata:

beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
    $scope = _$rootScope_;
    $compile = _$compile_;
    $templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
Jared
sumber
26
Inilah keluhan saya dengan metode ini ... Sekarang jika kita akan memiliki bagian besar dari html yang akan kita masukkan sebagai string ke dalam cache template lalu apa yang akan kita lakukan ketika kita mengubah html di front end ? Ubah html dalam pengujian juga? IMO itu adalah jawaban yang tidak berkelanjutan dan alasan kami menggunakan template di atas opsi templateUrl. Meskipun saya sangat tidak suka memiliki html saya sebagai string besar dalam direktif - ini adalah solusi paling berkelanjutan untuk tidak harus memperbarui dua tempat html. Yang tidak membutuhkan banyak pencitraan yang dapat dari waktu ke waktu html tidak cocok.
Sten Muchow
12

Masalah awal ini dapat diselesaikan dengan menambahkan ini:

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

Itu karena mencoba menemukan $ httpBackend di modul ngMock secara default dan tidak penuh.

bullgare
sumber
1
Nah, itulah jawaban yang benar untuk pertanyaan asli (itulah yang membantu saya).
Mat
Sudah mencoba ini, tetapi passThrough () masih tidak berhasil untuk saya. Itu masih memberikan kesalahan "Permintaan tak terduga".
frodo2975
8

Solusi yang saya capai membutuhkan jasmine-jquery.js dan server proxy.

Saya mengikuti langkah-langkah ini:

  1. Dalam karma.conf:

tambahkan jasmine-jquery.js ke file Anda

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

tambahkan server proxy yang akan melayani perlengkapan Anda

proxies = {
    '/' : 'http://localhost:3502/'
};
  1. Dalam spesifikasi Anda

    deskripsikan ('MySpec', function () {var $ scope, template; jasmine.getFixtures (). fixturesPath = 'public / partials /'; // jalur kustom sehingga Anda dapat menyajikan template sebenarnya yang Anda gunakan di aplikasi 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. Jalankan server di direktori root aplikasi Anda

    python -m SimpleHTTPServer 3502

  3. Jalankan karma.

Butuh beberapa saat untuk mengetahuinya, karena harus mencari banyak posting, saya pikir dokumentasi tentang ini harus lebih jelas, karena ini adalah masalah yang sangat penting.

Tomas Romero
sumber
Saya mengalami masalah saat menyajikan aset dari localhost/base/specsdan menambahkan server proxy dengan python -m SimpleHTTPServer 3502menjalankan perbaikannya. Anda tuan adalah seorang jenius!
pbojinov
Saya mendapatkan elemen kosong yang dikembalikan dari $ compile dalam pengujian saya. Tempat lain menyarankan untuk menjalankan $ scope. $ Digest (): masih kosong. Menjalankan $ scope. $ Apply () berhasil. Saya pikir itu karena saya menggunakan pengontrol dalam direktif saya? Tidak yakin. Terima kasih atas sarannya! Membantu!
Sam Simmons
7

Solusi saya:

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

ujian:

'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');
  }));
});
bartek
sumber
Solusi pertama yang layak yang tidak mencoba memaksa developer untuk menggunakan Karma. Mengapa pria bersudut melakukan sesuatu yang begitu buruk dan mudah dihindari di tengah-tengah sesuatu yang sangat keren? pfff
Fabio Milheiro
Saya melihat Anda menambahkan 'test / mock / ** / *. Js' dan saya kira itu untuk memuat semua hal yang diejek seperti layanan dan semuanya? Saya mencari cara untuk menghindari duplikasi kode dari layanan yang dibuat-buat. Bisakah Anda menunjukkan lebih banyak kepada kami tentang itu?
Stephane
tidak ingat persis, tetapi ada pengaturan yang bermasalah misalnya JSON untuk layanan $ http. Tidak ada yang mewah.
bartek
Punya masalah ini hari ini - solusi bagus. Kami menggunakan karma tapi kami juga menggunakan Chutzpah - tidak ada alasan kami harus dipaksa menggunakan karma dan hanya karma untuk dapat unit test directives.
lwalden
Kami menggunakan Django dengan Angular, dan ini bekerja seperti pesona untuk menguji direktif yang memuat templateUrl-nya static, misalnya beforeEach(preloadTemplate(static_url +'seed/partials/beChartDropdown.html')); Terima kasih!
Aleck Landgraf
6

Jika Anda menggunakan Grunt, Anda dapat menggunakan grunt-angular-templates. Ini memuat template Anda di templateCache dan transparan dengan konfigurasi spesifikasi Anda.

Contoh konfigurasi saya:

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

};
Tomas Romero
sumber
6

Saya memecahkan masalah yang sama dengan cara yang sedikit berbeda dari solusi yang dipilih.

  1. Pertama, saya menginstal dan mengkonfigurasi plugin ng-html2js untuk karma. Di file 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. Lalu saya memuat modul yang dibuat di beforeEach. Di file Spec.js Anda:

    beforeEach(module('myApp', 'to/templates/myTemplate.html'));
  3. Lalu saya menggunakan $ templateCache.get untuk menyimpannya ke dalam variabel. Di file Spec.js Anda:

    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. Akhirnya saya mengujinya dengan cara ini. Di file Spec.js Anda:

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

Untuk memuat html template secara dinamis ke $ templateCache Anda bisa menggunakan pra-prosesor karma html2js, seperti yang dijelaskan di sini

intinya adalah menambahkan template ' .html' ke file Anda di file conf.js juga preprocessors = {' .html': 'html2js'};

dan gunakan

beforeEach(module('..'));

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

ke dalam file pengujian js Anda

Lior
sumber
Saya mendapatkanUncaught SyntaxError: Unexpected token <
Melbourne2991
2

jika Anda menggunakan Karma, pertimbangkan untuk menggunakan karma-ng-html2js-preprocessor untuk melakukan pra-kompilasi template HTML eksternal Anda dan hindari Angular mencoba HTTP GET selama eksekusi pengujian. Saya berjuang dengan ini untuk beberapa milik kami - dalam kasus saya, jalur parsial templateUrl diselesaikan selama eksekusi aplikasi normal tetapi tidak selama pengujian - karena perbedaan dalam struktur app vs. test dir.

Nikita
sumber
2

Jika Anda menggunakan plugin jasmine-maven bersama - sama dengan RequireJS, Anda dapat menggunakan plugin teks untuk memuat konten template ke dalam variabel, lalu meletakkannya di cache template.


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

    });
});
Leonard Brünings
sumber
Bisakah Anda melakukan ini tanpa Karma?
Winnemucca
2

Jika Anda menggunakan requirejs dalam pengujian Anda, maka Anda dapat menggunakan plugin 'text' untuk menarik template html dan meletakkannya di $ 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();
    }));
  });
});
Tim Kindberg
sumber
0

Saya menyelesaikan masalah ini dengan menyusun semua template ke templatecache. Saya menggunakan teguk, Anda dapat menemukan solusi serupa untuk mendengus juga. TemplateUrls saya dalam direktif, modals terlihat seperti

`templateUrl: '/templates/directives/sidebar/tree.html'`
  1. Tambahkan paket npm baru di my package.json

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

  2. Di file gulp tambahkan templatecache dan tugas baru:

    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. Tambahkan semua file js di index.html

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

  4. Nikmati!

kitolog
sumber