user2871401
user2871401

Reputation: 1877

Unit Testing Directive that has a Controller in AngularJS

I am working on an AngularJS app. I want to be able to implement unit testing through this app. Currently, I'm struggling with unit testing one of my directives. At this time, I have a module setup as follows:

angular.module('my.module', [])
  .controller('myCtrl', function ($scope, $element) {
    // Directive specific business logic goes here
  })
  .directive('myDirective', function() {
    return {
      restrict: 'E',
      transclude: true,
      replace: true,
      scope: {
        title:'=',
    state:'@'
      },
      templateUrl: 'myHtml.tpl.html',
      controller: myCtrl
    };
  })
;

I have split my controller out from my directive because I need to be able to unit test the controller. This code works in the app itself. However, I run into a problem when I attempt to unit test it. I run into issues because I can't figure out how to inject the $element into the controller from a unit test. Currently, I have the following tests setup:

describe('my.module', function () {
    var $scope;
    var myCtrl;

    beforeEach(module('myApp'));
    beforeEach(inject(function ($controller, $rootScope, $element) {
        $scope = $rootScope.$new();
        myCtrl = $controller('myCtrl', { $scope: $scope });
    }));

    it('should create controller', inject(function () {
      expect(testCtrl).toBeDefined();
    }));

    it('should create the directive', inject(function ($rootScope, $compile, $log) {
      var d = angular.element('<my-directive></my-directive>');
      $compile(d)($rootScope);
      $scope.$digest();

      expect($log.assertEmpty).not.toThrow();
    }));
});

$element is something that automatically gets injected into a directive. However, I can't figure out how to inject this into a controller. I need to do this so I can unit test it. How do I do this?

Thank you!

Upvotes: 1

Views: 2514

Answers (2)

Sten Muchow
Sten Muchow

Reputation: 6701

What I can recommend in unit testing of controllers is to only worry about the logic that the controller exposes. You are trying to test the logic of the directive in the controller test i.e. through $element.

Mock this in the controller test, as element can be anything. Your controller shouldn't care about the DOM; it's a controller and it's main (maybe only) job is to create a scope and expose "things" to the scope - functions, variables, logic. They shouldn't be bound to the logic of the directive or else the coupling is too tight. The controller should freely be able to receive input, correct input that is, and work with the input to give you something back as output.

If you can give an example of the logic your controller exposes then I can help further in composing the unit tests.

Update:

Now looking at the unit test you have provided. You need to separate the logic out of the unit test for the controller and directive.

Upvotes: 2

Snoozer
Snoozer

Reputation: 595

for inject $element and $log do as it :

describe('my.module', function ($compile) {
    var $scope, $controller, $element, $log, $compile, html;

    beforeEach(module('myApp'));
    // an another module ...

    beforeEach(inject(function ($injector) {
       $scope = $injector.get('$rootScope');
       $controller = $injector.get('$controller');
       $element = $injector.get('$element');
       $log = $injector.get('$log');
       $compile = $injector.get('$compile');

       function createController (){
          return $controller('MyCtrl', {'$scope' : $scope, '$element' : $element, '$log': $log });
       }
       // init your controller
       createController();

    }));

    it('should create the directive',function () {
    // possibility to use $scope, $element, $log in your test
      $compile('<my-directive></my-directive>')($scope);
      $scope.$digest();

      expect($log.assertEmpty).not.toThrow();
    });
});

Upvotes: 1

Related Questions