How to inject a controller into a directive when unit-testing

All we need is an easy explanation of the problem, so here it is.

I want to test an AngularJS directive declared like this

app.directive('myCustomer', function() {
    return {
      template: 'cust.html'
      controller: 'customerController'
    };
  });

In the test I would like to inject (or override) the controller, so that I can test just the other parts of the directive (e.g. the template). The customerController can of course be tested separately. This way I get a clean separation of tests.

  • I have tried overriding the controller by setting the controller property in the test.
  • I have tried injecting the customController using $provide.
  • I have tried setting ng-controller on the html directive declaration used in the test.

I couldn’t get any of those to work. The problem seems to be that I cannot get a reference to the directive until I have $compiled it. But after compilation, the controller is already set up.

 var element = $compile("<my-customer></my-customer>")($rootScope);

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

One way is to define a new module (e.g. ‘specApp’) that declares your app (e.g. ‘myApp’) as a dependency. Then register a ‘customerController’ controller with the ‘specApp’ module. This will effectively “hide” the customerController of ‘myApp’ and supply this mock-controller to the directive when compiled.
E.g.:

Your app:

var app = angular.module('myApp', []);
...
app.controller('customerController', function ($scope,...) {
    $scope = {...};
    ...
});
app.directive('myCustomer', function () {
    return {
        template: 'cust.html',
        controller: 'customerController'
    };
});

Your spec:

describe('"myCustomer" directive', function () {
    $compile;
    $newScope;

    angular.module('specApp', ['myApp'])
    /* Register a "new" customerController, which will "hide" that of 'myApp' */
    .controller('customerController', function ($scope,...) {
        $scope = {...};
        ...
    });

    beforeEach(module('specApp'));

    it('should do cool stuff', function () {
        var elem = angular.element('<div my-customer></div>');
        $compile(elem)($newScope);
        $newScope.$digest();
        expect(...
    });
});

See, also, this working demo.

Method 2

I think there is a simpler way than the accepted answer, which doesn’t require creating a new module.

You were close when trying $provide, but for mocking controllers, you use something different: $controllerProvider. Use the register() method in your spec to mock out your controller.

beforeEach(module('myApp', function($controllerProvider) {
    $controllerProvider.register('customerContoller', function($scope) {
        // Controller Mock
    });
});

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply