8

Unit testing an angular directive is not very hard, but I found out that there are different ways to do it.

For the purpose of this post, lets assume the following directive

angular.module('myApp')
    .directive('barFoo', function () {
        return {
            restrict: 'E',
            scope: true,
            template: '<p ng-click="toggle()"><span ng-hide="active">Bar Foo</span></p>',
            controller: function ($element, $scope) {
                this.toggle() {
                    this.active = !this.active;
                }
            }
        };
    });

Now I can think of two ways to unit test this

Method 1:

describe('Directive: barFoo', function () {
    ...
    beforeEach(inject(function($rootScope, barFooDirective) {
        element = angular.element('<bar-foo></bar-foo>');
        scope = $rootScope.$new();
        controller = new barFooDirective[0].controller(element, scope);
    }));

    it('should be visible when toggled', function () {
        controller.toggle();
        expect(controller.active).toBeTruthy();
    });
});

Method 2:

beforeEach(inject(function ($compile, $rootScope) {
    element = angular.element('<bar-foo></bar-foo>');
    scope = $rootScope.$new();
    $compile(element)(scope);
    scope.$digest();
}));

it ('should be visible when toggled', function () {
    element.click();
    expect(element.find('span')).not.toHaveClass('ng-hide');
});

So, I'm curious what the pro's and cons are of both methods and which one is most robust ?

1
  • 1
    I assume that clicking on element in unit testing is like test controller methods in protractor Commented Nov 17, 2015 at 16:05

3 Answers 3

2

Here is how you test your AngularJS directive :

describe('Directive: barFoo', function () {
  var createElement, element, parentScope, directiveScope;
  
  beforeEach(module('myApp'));

  beforeEach(inject(function($injector) {
    var $compile = $injector.get('$compile');
    var $rootScope = $injector.get('$rootScope'),

    parentScope = $rootScope.$new();
    parentScope.paramXyz = ...  <-- prepare whatever is expected from parent scope

    createElement = function () {
      element = $compile('<bar-foo></bar-foo>')(parentScope);
      directiveScope = element.isolateScope();
      parentScope.$digest();
      $httpBackend.flush(); <-- if needed
    };
  }));


  it('should do XYZ', function() {
    parentScope.xyz = ...    <-- optionnal : adjust scope according to your test
    createElement();

    expect(...) <-- whatever, examples :

    var submitButton = element.find('button[type="submit"]');
    expect( submitButton ).to.have.value('Validate');
    expect( submitButton ).to.be.disabled;
    submitButton.click();
  });

Sign up to request clarification or add additional context in comments.

3 Comments

This approach is more or less the same as method 2. Could you provide some argumentation why this is better than method 1 ?
@JeanlucaScaljeri in method 1) you have to create the controller manually and in method 2) you have an extra step. The example I propose (taken from our actual tests in my company) also displays a test structure which allows easier testing, by allowing some extra inits before instantiating the directive with createElement(). Also note the clear separation between parent and child scope.
I like this approach. I am new to testing directives. I am wondering how you identify one specific directive to test? Is that necessary?
1

I find the first method more "correct" because it doesnt depend on a clicking event. I believe that if you want to test the clicking of an element and the effects of it you should use protractor and use jasmine only for unit tests. In that way you will have a nice separation between the unit tests and the UI tests.

Also it makes the test more maintable. e.g. if you decide to fire toggle on hover instead of on click in the second method you will have to update the tests as well.

4 Comments

You assume you ment the first method because the second method does the .click() thing ?
@JeanlucaScaljeri yap, I meant the first one. I fixed it. thx.
Unit testing created for testing methods returns and impact on data and behavior driven tests (clicks etc.) are used to test actual user interactions.
So what you're saying is that both are valid testing approaches ?
1

What I like to do is to create stories of my test like this dummy example.

'use strict';

describe('app controller', function() {

  var scope;

  ...   

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

  it('should have properties defined', function() {

    expect(scope.user).toEqual(user);
    ...    

  });

  it('should have getFaqUrl method', function() {

    expect(scope.getFaqUrl).toEqual(refdataService.getFaqUrl);

  });

  it('should signout and delete user data from local storage', function() {

    ...

  });

});

So I guess you just didn't state it in second example, but in case you did, use describe enclosure always when testing, just a good practice.

As for the test themselves, I would advise on avoiding the method where you explicitly call for scope.$digest(), especially as it does not seem necessary for the purpose of your test.

Shortly, I would go for method 1.

2 Comments

Thanks a lot for the reply. Could you update you post with an example of how your test looks like in combination with a story ?
Sorry I only edited now, I was looking in some dummy example, as I can't share prod code.. but this explain what I was thinking about shorty, and for each controller, adding a story like sets

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.