3

I'm having trouble unit-testing directives that make use of templateUrl.

There's this awesome tutorial about AngularJS testing [1] and it even has a Github repo that goes with it [2]

So I forked it [3] and made the following changes:

On directives.js I created two new directives:

.directive('helloWorld', function() {
    return {
      restrict: 'E',
      replace: true,
      scope:{},
      template: '<div>hello world!</div>',
      controller: ['$scope', function ($scope) {}]
    }
})

.directive('helloWorld2', function() {
    return {
      restrict: 'E',
      replace: true,
      scope:{},
      templateUrl: 'mytemplate.html',
      controller: ['$scope', function ($scope) {}]
    }
})

and I changed test/unit/directives/directivesSpecs.js so that it loads a template into $templateCache and then added two more tests for the new directives:

//
// test/unit/directives/directivesSpec.js
//
describe("Unit: Testing Directives", function() {

  var $compile, $rootScope, $templateCache;

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

  beforeEach(inject(
    ['$compile','$rootScope', '$templateCache', function($c, $r, $tc) {
      $compile = $c;
      $rootScope = $r;

      //Added $templateCache and mytemplate
      $templateCache = $tc;
      $templateCache.put('mytemplate.html', '<div>hello world 2!</div>');
    }]
  ));

  //This was already here
  it("should display the welcome text properly", function() {
    var element = $compile('<div data-app-welcome>User</div>')($rootScope);
    expect(element.html()).to.match(/Welcome/i);
  });


  //Added this test - it passes
  it("should render inline templates", function() {
    var element = $compile('<hello-world></hello-world>')($rootScope);
    expect(element.text()).equal("hello world!");
  });

  //Added this test - it fails
  it("should render cached templates", function() {
    var element = $compile('<hello-world2></hello-world2>')($rootScope);
    expect(element.text()).equal("hello world 2!");
  });

});

The last test fails because Angular won't compile the template like it should.

$ grunt test:unit
Running "karma:unit" (karma) task
INFO [karma]: Karma v0.10.10 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 35.0.1916 (Linux)]: Connected on socket ChISVr0ZABZ1fusdyv3m
Chrome 35.0.1916 (Linux) Unit: Testing Directives should render cached templates FAILED
    expected '' to equal 'hello world 2!'
    AssertionError: expected '' to equal 'hello world 2!'
Chrome 35.0.1916 (Linux): Executed 18 of 18 (1 FAILED) (0.441 secs / 0.116 secs)
Warning: Task "karma:unit" failed. Use --force to continue.

Aborted due to warnings.

I'm pretty sure this was supposed to work. At least, it's very similar with the solution proposed by @SleepyMurth on [4].

But I feel I reached the limit of understanding what's going wrong with my current knowledge of AngularJS.

HELP! :-)

[1] http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html

[2] https://github.com/yearofmoo-articles/AngularJS-Testing-Article/

[3] https://github.com/tonylampada/AngularJS-Testing-Article

[4] Unit Testing AngularJS directive with templateUrl

Tony Lâmpada
  • 5,301
  • 6
  • 38
  • 50

1 Answers1

10

The problem

When templateUrl is specified, templates are fetched using $http (even for cached templates, which $http serves from the $templateCache). For that reason, there needs to be a $digest cycle for the $http's promise to be resolved with the template content and processed by the directive.


The solution

Since promises get resolved during a $digest cycle (and since we are outside of an "Angular context"), we need to manually invoke $rootScope.$digest() before evaluating our assertions.
Thus, the last test should be modified like this:

it("should render cached templates", function() {
    var element = $compile('<hello-world2></hello-world2>')($rootScope);
    $rootScope.$digest();   // <-- manually force a $digest cycle
    expect(element.text()).toBe(tmplText);
});

See, also, this short demo.

Community
  • 1
  • 1
gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • I love this website! Have an upvote sir. Thanks a bunch! – Tony Lâmpada May 26 '14 at 17:19
  • I just want wanted to come back here and restate how impressed I am with the speed and quality of this answer. Geeze! :-) – Tony Lâmpada May 27 '14 at 17:17
  • I've setup the templateCache, compiled my element, digest the scope it's compiled into and yet the directive element is not replaced by the template. =( I get no errors, it just doesn't compile it during unit testing. – FlavorScape Aug 07 '14 at 17:24
  • @FlavorScape: Post a question with the relevant code and (if possible) a reproducing fiddle or plunkr. – gkalpak Aug 07 '14 at 17:34
  • I could make a plunkr, but my relevant code is pretty much the same.Except if it contains an ng-repeat... there's just a commented out – FlavorScape Aug 07 '14 at 17:38
  • @FlavorScape: Please, do post a new question with plunkr. You can post a link here and I will take a look. – gkalpak Aug 07 '14 at 17:52
  • Hm.. I guess my main difference is i do controllerAs and my ng-repeats depend on someCtrl.someList instead of $scope.someList-- Here's a question: http://stackoverflow.com/questions/25189304/how-to-access-controlleras-namespace-in-unit-test-with-compiled-element – FlavorScape Aug 07 '14 at 18:12
  • I guess i can't even get the element to compile in the controller. i updated my question with the correct fiddle. isRendered is still undefined, even though compile is called. – FlavorScape Aug 07 '14 at 18:53
  • yes, your example does not have a real controller. The controller will not work =( – FlavorScape Aug 07 '14 at 18:55