Mocking Controller Instantiation In Angular Directive Unit Test

Sten Muchow picture Sten Muchow · Apr 12, 2014 · Viewed 9.7k times · Source

I am unit testing an Angular directive and would like to mock or stub in some way the instantiation of the named controller in the unit test.

So first I suppose on to some code...

'use strict';

angular.module('App.Directives.BreadCrumbs', [])

    .directive('kxBreadcrumbs', function () {
        return {
            restrict: 'E',
            controller: 'BreadCrumbsController',
            template:
                '<!-- Breadcrumbs Directive HTML -->' +

                '<ol class="breadcrumb">' +
                '    <li ng-repeat="crumb in crumbPath">' +
                '       <a ng-class="{true: \'disable\', false: \'\'}[crumb.last]" href="{{crumb.href}}" ng-click="updateCrumb(crumb.name)">{{crumb.name}}</a>' +
                '    </li>' +
                '</ol>' +

                '<!-- End of Breadcrumbs Driective HTML -->'
        };

    });

This is one sample directive that I would unit test, the important thing to take away from this is the named controller.

So in my unit test

'use strict';

describe('Directives: Breadcrumbs', function () {

    var//iable declarations
        elm,
        scope,
        $rootScope
    ;

    beforeEach(function () {
        module('App.Directives.BreadCrumbs');
        module('App.Controllers.BreadCrumbs');
        module('App.Constants'); // <--- Comes from the controller dependancy
    });


    beforeEach(inject(function (_$rootScope_, $compile) {
        $rootScope = _$rootScope_;
        scope = $rootScope.$new();

        elm = angular.element('<kx-breadcrumbs></kx-breadcrumbs>');
        $compile(elm)(scope);
        scope.$apply();
    }));

    it('Should create the breadcrumbs template', function () {
        scope.crumbPath = [{name: 'home', href: '/'},{name: 'coffee', href: '/coffee'},{name: 'milk', href: '/milk'}];
        scope.$apply();
        var listItem = $(elm).find('li');
        expect(listItem.length).toBe(3);
        expect($(listItem).text()).toContain('home');
        expect($(listItem).text()).toContain('coffee');
        expect($(listItem).text()).toContain('milk');

    });
});

You can see the inclusion of the 3 modules - the directive, the controller and the third one the constants. This is referenced by the controller as a dependancy so in order to pull this into the unit test I need to pull in the dependancy or in much worse cases the dependancies from the controller. But as I am not unit testing the functionality of the controller in the directive unit test, this seem redundant and bloating of code through inclusion of modules. Ideally I would like to only include the module that I am unit testing.

  module('App.Directives.BreadCrumbs');

and not (modules added to exemplify my point more)

  module('App.Directives.BreadCrumbs');
  module('App.Controllers.BreadCrumbs');
  module('App.Constants'); // <--- Comes from the controller dependancy
  module('App.Service.SomeService'); // <--- Comes from the controller dependancy
  module('App.Service.SomeOtherService'); // <--- Comes from the SomeService dependancy

When we unit test controllers we can mock services that are passed in either completely or by using jasmine spies. Can we accomplish the same sorta thing in unit test of directives so I don't have to follow the dependancy trail?

Answer

Vadim picture Vadim · Apr 12, 2014

You can create mocks in module configuration block by using $controllerProvider.register() for controllers, $provide.provider(), $provide.factory(), $provide.service() and $provide.value() for providers, factories and services:

JavaScript

beforeEach(function () {
    module('App.Directives.BreadCrumbs', function($provide, $controllerProvider) {
        $controllerProvider.register('BreadCrumbsController', function($scope) {
            // Controller Mock
        });
        $provide.factory('someService', function() {
            // Service/Factory Mock
            return {
                doSomething: function() {}
            }
        });
    });
});

Once you do so, Angular will inject your mock BreadCrumbsController controller into kxBreadcrumbs directive. This way you don't need to include real controller and it's dependencies into unit test.

For more information see Angular's official documentation on: