Angularjs – unit testing directives that require controllers

It has been a rough day, trying to do it, and reading lots of methods on how to do it.  To make a long story longer, we were looking for a way to test a directive that requires a parent’s controller, without using the (still none existent) parent.

For instance, let’s take this example:

angular.module('nestedDirective')
    .directive('fatherDirective', function(){
        return {
            controller: function(){},
            controllerAs: 'fatherCtrl',
            template: '<child-directive></child-directive>'
        }
    })
    .directive('childDirective', function(){
        return {
            controller: function(){},
            controllerAs: 'childCtrl',
            template: '<div></div>',
            require: '^^fatherDirective'
        }
    });

In many cases, we saw people offering to do the following test:

describe('test childDirective', function(){
    var $rootScope, $compile,
            scope, element, ctrl;
    beforeEach(module('nestedDirectives'));
    beforeEach(
        inject(function(_$rootScope_, _$compile_){
            $rootScope = _$rootScope_;
            $compile = _$compile_;
            
            scope = $rootScope.$new();
            element = $compile(angular.element('<father-directive></father-directive>'))($scope);
             // get the child directive's element
            element = element.find('child-directive');

            // get the child directive's controller
            ctrl = element.scope().childCtrl;
        })
    ) 
})

While this method works, it is not what we wanted. Assume that fatherDirective also requires its parent? Or maybe several parents? Do I need to mock all the data all the way to the <html> tag?

The way we found was to mock the parent’s controller, and add it to some div element that wraps the childDirective:

describe('test childDirective', function(){
    var $rootScope, $compile,
            scope, element, ctrl;

    var mockedFatherCtrl = {

    };

    beforeEach(module('nestedDirectives'));
    beforeEach(
        inject(function(_$rootScope_, _$compile_){
            $rootScope = _$rootScope_;
            $compile = _$compile_;

            scope = $rootScope.$new();
            element = $compile(angular.element('<div><child-directive></child-directive></div>'))($scope);
            // add the mock controller to the father
            element.data('$fatherDirectiveController', mockedFatherCtrl);

            // get the child directive's element
            element = element.find('child-directive');

            // get the child directive's controller
            ctrl = element.scope().childCtrl;
        })
    )
})

It appears that angular adds the directives’ controllers to the directives’ elements and you can access them all via the element.data() that will list them for you.  The syntax is:

‘$’ + directiveName + ‘Controller’

So the line

element.data('$fatherDirectiveController', mockedFatherCtrl);

actually adds the mocked controller as the father directive’s controller that the child directive is looking for via the require attribute.  This way, you can do real isolated testing for directives that require controllers.

2 thoughts on “Angularjs – unit testing directives that require controllers

  1. Rupesh Kumar Tiwari

    Thank you very much it helped me while testing an directive which had parent directive dependencies. I wanted to ignore all of the parent dependencies. I mocked the parent controller part and it worked.

    element.data(‘$fatherDirectiveController’, mockedFatherCtrl);
    is nice trick.

Leave a Reply