Jasmine tests AngularJS Directives with templateUrl

39,852

Solution 1

If you're using ngMockE2E or ngMock:

all HTTP requests are processed locally using rules you specify and none are passed to the server. Since templates are requested via HTTP, they too are processed locally. Since you did not specify anything to do when your app tries to connect to views/currency-select.html, it tells you it doesn't know how to handle it. You can easily tell ngMockE2E to pass along your template request:

$httpBackend.whenGET('views/currency-select.html').passThrough();

Remember that you can also use regular expressions in your routing paths to pass through all templates if you'd like.

The docs discuss this in more detail: http://docs.angularjs.org/api/ngMockE2E.$httpBackend

Otherwise use this:

You'll need to use the $injector to access the new backend. From the linked docs:

var $httpBackend;
beforeEach(inject(function($injector) {
  $httpBackend = $injector.get('$httpBackend');
  $httpBackend.whenGET('views/currency-select.html').respond(200, '');
}));

Solution 2

the Karma way is to load the template html dynamically into $templateCache. you could just use html2js karma pre-processor, as explained here

this boils down to adding templates '.html' to your files in the conf.js file as well preprocessors = { '.html': 'html2js' };

and use

beforeEach(module('..'));

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

into your js testing file

Solution 3

If this is a unit-test, you won't have access to $httpBackend.passthrough(). That's only available in ngMock2E2, for end-to-end testing. I agree with the answers involving ng-html2js (used to be named html2js) but I would like to expand on them to provide a full solution here.

To render your directive, Angular uses $http.get() to fetch your template from templateUrl. Because this is unit-testing and angular-mocks is loaded, angular-mocks intercepts the call to $http.get() and give you the Unexpected request: GET error. You can try to find ways to by pass this, but it's much simpler to just use angular's $templateCache to preload your templates. This way, $http.get() won't even be an issue.

That's what the ng-html2js preprocessor do for you. To put it to work, first install it:

$ npm install karma-ng-html2js-preprocessor --save-dev

Then configure it by adding/updating the following fields in your karma.conf.js

{
    files: [
      //
      // all your other files
      //

      //your htmp templates, assuming they're all under the templates dir
      'templates/**/*.html'
    ],

    preprocessors: {
        //
        // your other preprocessors
        //

        //
        // tell karma to use the ng-html2js preprocessor
        "templates/**/*.html": "ng-html2js"
    },

    ngHtml2JsPreprocessor: {
        //
        // Make up a module name to contain your templates.
        // We will use this name in the jasmine test code.
        // For advanced configs, see https://github.com/karma-runner/karma-ng-html2js-preprocessor
        moduleName: 'test-templates',
    }
}

Finally, in your test code, use the test-templates module that you've just created. Just add test-templates to the module call that you typically make in beforeEach, like this:

beforeEach(module('myapp', 'test-templates'));

It should be smooth sailing from here on out. For a more in depth look at this and other directive testing scenarios, check out this post

Solution 4

You could perhaps get the $templatecache from the injector and then do something like

$templateCache.put("views/currency-select.html","<div.....>");

where in place of <div.....> you would be putting your template.

After that you setup your directive and it should work just fine!

Solution 5

As requested, converting a comment to an answer.


For the people who want to make use of @Lior's answer in Yeoman apps:

Sometimes the way the templates are referenced in karma config and consequently - the names of modules produced by ng-html2js don't match the values specified as templateUrls in directive definitions.
You will need adjusting generated module names to match templateUrls.
These might be helpful:

Share:
39,852
Tane Piper
Author by

Tane Piper

A full stack Javascript developer with over 10 years of professional development experience. I have been working with server side Javascript since 2010 and have released open source modules as well as developed client projects. On the client side I've been working on a variety of projects since 2006 using jQuery, Angular and React. Outside of work I am a keen scuba diver and amateur photographer.

Updated on September 29, 2020

Comments

  • Tane Piper
    Tane Piper over 3 years

    I'm writing directive tests for AngularJS with Jasmine, and using templateUrl with them: https://gist.github.com/tanepiper/62bd10125e8408def5cc

    However, when I run the test I get the error included in the gist:

    Error: Unexpected request: GET views/currency-select.html
    

    From what I've read in the docs I thought I was doing this correctly, but it doesn't seem so - what am I missing here?

    Thanks

  • Tane Piper
    Tane Piper over 11 years
    Hmm, I've given this a try but it seems that after I inject it, passThrough is not available as a function: TypeError: 'undefined' is not a function (evaluating '$httpBackend.when('GET', 'views/currency-select.html').passThrough()') I've also included beforeEach(module('ngMockE2E')); at the top of my file and it goes back to the original error
  • Tane Piper
    Tane Piper over 11 years
    I went with something similar using $httpBackend doing $httpBackend.when('GET', 'views/currency-select.html').respond('<select ng-options="currency.name for currency in currencies" ng-model="selected_currency"></select>'); - however it kind of defeats DRY - I want it to load my templates, not have to repeat them in code again.
  • Kaitsu
    Kaitsu almost 11 years
    Here's a fairly good explanation how the templateCache-way works: portlandwebworks.com/blog/…. Just note that the blog speaks about a fairly old version of Karma, so today you'd need to use ng-html2js instead of html2js for preprocessing the templates to js.
  • Sten Muchow
    Sten Muchow about 10 years
    There is no passThrough() method in a $httpBackend Mock. Its says it right in the docs. docs.angularjs.org/api/ngMock/service/$httpBackend Thats why your getting the function not available error. Now if you want to mock this in the frontend and the in unit testing you have the passThrough method available - just not in unit testing...
  • Josh David Miller
    Josh David Miller about 10 years
    @StenMuchow My answer and @tanepiper's problem are not for the ngMock module, but the ngMockE2E module, which does support the passThrough(). Typically, one wouldn't use it during unit tests because unit tests shouldn't need any HTTP requests like templates, but in the case that they do and the build does not compile them, the E2E backend can be used.
  • Josh David Miller
    Josh David Miller about 10 years
    @StenMuchow The answer linked to the correct page of the documentation, but to avoid future confusion I removed the part that confused you.
  • Nicholas Murray
    Nicholas Murray almost 10 years
    having the file paths different was causing this error for me
  • Cameron
    Cameron over 9 years
    @JoshDavidMiller pretty sure passThrough() is deprecated, but this solution is still not complete. One must actually use ngHtml2JsPreprocessor and set the path to the sample path as the templateUrl in the directive.
  • Josh David Miller
    Josh David Miller over 9 years
    @cameronjroe I'm still unsure what you're trying to accomplish. According to the docs, passThrough() is not deprecated. I'm not sure why one would need to use a pre-processor to accomplish this; the OP simply wanted the XHR to go through to his backend to deliver the template for use during unit testing. Based on my understanding, passThrough() does precisely that. Can you provide an example or some documentation to indicate otherwise?
  • Cameron
    Cameron over 9 years
    @JoshDavidMiller you are correct in ngMockE2E this works, but not in ngMock. I prefer to use the pre-processor so that I can use external templates as modules, but you can change it back. I wasn't aware of the ngMockE2E modules. Apologies.
  • absynce
    absynce over 7 years
    Use beforeEach(module('ngMockE2E')); in order to get passThrough().
  • Brant
    Brant over 7 years
    Not great for long HTML templates but it gets me past where I've been stuck for a good few hours! Thanks!