15

I'm trying to test a Directive in Angular, but I can't get the corresponding template to work.

The directive lists the templateUrl like so

templateUrl: 'directives/listview/view.html'

Now when I write any unit-test, I get

Error: Unexpected request: GET directives/listview/view.html

So I have to use the $httpBackend and respond with something sensible like

httpBackend.whenGET('directives/listview/view.html').respond("<div>som</div>");

But really I want to simply return the actual file, and also do it synchronously, so there's no issues with waits, deferred objects etc. How to do that?

Willem Mulder
  • 12,974
  • 3
  • 37
  • 62

2 Answers2

13

I now use https://github.com/karma-runner/karma-ng-html2js-preprocessor. What it does is reading in all the templates that you use, convert them to Angular templates, and set them on the $templateCache, so when your app needs them, it will retrieve them from cache, and not request them from the server.

In my karma conf file

files: [
    // templates
    '../**/*.html'
],

preprocessors : {
  // generate js files from html templates
  '../**/*.html': 'ng-html2js'
},

ngHtml2JsPreprocessor: {
    // setting this option will create only a single module that contains templates
    // from all the files, so you can load them all with module('templates')
    moduleName: 'templates'
},

And then in the test, do like

// Load templates
angular.mock.module('templates');

And it works!

Willem Mulder
  • 12,974
  • 3
  • 37
  • 62
  • 2
    Hi there. I'm having some problems on loading the templates. I followed the instructions which are pretty clear, but the test is still trying to load the template. Here the code if you have the time to check https://gist.github.com/andreareginato/7168181 – Andrea Reginato Oct 26 '13 at 11:09
  • 3
    Probably you need to set something like cacheIdFromPath : function(filepath) { return filepath.substr(filepath.indexOf("appname")+8); }, in the ngHtml2JsPreprocessor part. Because the paths under which it caches the templates are by default relative to *disk*, while what we want is to have them relative to our app root. Also see http://willemmulder.tumblr.com/post/63827986070/unit-testing-angular-modules-and-directives-with for a more elaborate instruction – Willem Mulder Oct 28 '13 at 06:57
  • 2
    Your suggestion was right. It was a path problem. I solved it in this way https://github.com/angular/angular.js/issues/2512#issuecomment-27146056 – Andrea Reginato Oct 28 '13 at 13:51
  • That also works, yes! Great that you got it to work :-) Any upvote on the answer also appreciated. – Willem Mulder Oct 28 '13 at 16:09
  • @WillemMulder while this answer may work for you scenario imo the better answer is the one by blesh which correctly answers the question as it was phrased - which is what people are coming to this question for. – cyberwombat Nov 03 '13 at 18:49
  • 3
    I needed to add `stripPrefix: 'app/'` to the `ngHtml2JsPreprocessor` karma config. – Leopd Feb 12 '14 at 22:10
  • I run my unit tests in Jasmine test runner. I believed could be the same but soon, there's no way to let it work without Karma runner. And why? Because the want passThrough only for e2e tests... Sorry but the directive with its template is a single unit of software and I want to test it. – Plap Jun 24 '14 at 19:54
10

Be sure to include the ngMockE2E module in your beforeEach

If you don't the $browser service mock will not be instantiated when whenGET is called, and the return value will not set up the passThrough function

beforeEach(function() {
   module('yourModule');
   module('ngMockE2E'); //<-- IMPORTANT!

   inject(function(_$httpBackend_) {
    $httpBackend = _$httpBackend_;
    $httpBackend.whenGET('somefile.html').passThrough();
   });
});

The place in angular-mocks.js where this is set up:

The source code in question is in $httpBackend mock's when function:

function (method, url, data, headers) {
  var definition = new MockHttpExpectation(method, url, data, headers),
      chain = {
        respond: function(status, data, headers) {
          definition.response = createResponse(status, data, headers);
        }
      };

  if ($browser) {
    chain.passThrough = function() {
      definition.passThrough = true;
    };
  }
  definitions.push(definition);
  return chain;
} 
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 3
    That unfortunately only works with e2e testing, not unit testing. – Willem Mulder Oct 11 '13 at 14:29
  • 1
    If it doesn't work in your unit tests, you might have your unit tests set up strangely. I use this all the time in my unit tests. For e2e testing, I'm not even using angular-mocks, anyhow, I use protractor for that. – Ben Lesh Oct 11 '13 at 14:31
  • 2
    In the karma config, I include angular, angular-mock and my project files. Then in the test, I can use $httpBackend, but when I try to use .passThrough() I get TypeError: 'undefined' is not a function evaluating 'httpBackend.whenGET(/.*/).passThrough()'). Do you include special dependencies to make it work? – Willem Mulder Oct 11 '13 at 14:36
  • Updated this with a more complete answer. – Ben Lesh Oct 11 '13 at 16:04
  • 17
    Thanks. I tried that, but the requests don't seem to get served. I get "Error: Unexpected request: GET directives/listview/view.html. No more request expected", while the rule that I set up is httpBackend.whenGET("directives/listview/view.html").passThrough(); which should work, right? – Willem Mulder Oct 12 '13 at 13:38