Is it possible for jasmine to access the scope methods defined in an angular directive controller?

963 views Asked by At

I'm trying to unit test a directive like the one below. I want to be able to call the functions defined in the directive's controller ($scope.save), but my tests can't seem to access that scope at all.

I also tried isolateScope(), but that didn't return the inner $scope object like I expected. Maybe I did that wrong.

How can I get myDir's $scope in the example below? (or at this fiddle if you prefer: http://jsfiddle.net/lalanl/8fkdsme3/)

    angular.module('app', []).directive([
      function(myService) {

        return {
          restrict: 'E',
          templateUrl: 'path/to/template.html',
          scope: {
            info: "=info"
          },
          controller: function($scope) {
            $scope.someVal = 'porcupine';
            $scope.save = function() { /* ... */ };
          }
        }
      }
    ]);

    describe('myDir', function() {

      var $compile, $http, $httpBackend, $scope, $rootScope, $q;
      var element;

      beforeEach(module('app'));
      beforeEach(function() {
        inject(function(_$compile_, _$http_, _$httpBackend_, _$rootScope_, _$q_) {
          $compile = _$compile_;
          $http = _$http_;
          $httpBackend = _$httpBackend_;
          $rootScope = _$rootScope_;
          $scope = $rootScope.$new();
          $q = _$q_;
        });

        $scope.data = "some data";
        element = $compile(angular.element('<my-dir info="data"></my-dir>'), $scope);
        $rootScope.$digest();
        $httpBackend.whenGET('path/to/template.html').respond('<div>test:{{someVal}}</div>');
      });

      it('should let me see its guts', function() {
        expect($scope.save).toBeTruthy();
        expect(typeof $scope.save).toBe("function");
      });

    });
<html>

<head>
  <style type="text/css">
    @charset "UTF-8";
    [ng\:cloak],
    [ng-cloak],
    [data-ng-cloak],
    [x-ng-cloak],
    .ng-cloak,
    .x-ng-cloak,
    .ng-hide:not(.ng-hide-animate) {
      display: none !important;
    }
    ng\:form {
      display: block;
    }
  </style>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>SO question - jsFiddle demo</title>

  <script type="text/javascript" src="/js/lib/dummy.js"></script>




  <link rel="stylesheet" type="text/css" href="/css/result-light.css">


  <script type="text/javascript" src="http://jasmine.github.io/2.0/lib/jasmine.js"></script>



  <script type="text/javascript" src="http://jasmine.github.io/2.0/lib/jasmine-html.js"></script>



  <script type="text/javascript" src="http://jasmine.github.io/2.0/lib/boot.js"></script>



  <link rel="stylesheet" type="text/css" href="http://jasmine.github.io/2.0/lib/jasmine.css">



  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.4/angular.js"></script>



  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.4/angular-mocks.js"></script>


  <style type="text/css">
  </style>



  <script type="text/javascript">
    //<![CDATA[ 

    angular.module('app', []).directive([
      function(myService) {

        return {
          restrict: 'E',
          templateUrl: 'path/to/template.html',
          scope: {
            info: "=info"
          },
          controller: function($scope) {
            $scope.someVal = 'porcupine';
            $scope.save = function() { /* ... */ };
          }
        }
      }
    ]);

    describe('myDir', function() {

      var $compile, $http, $httpBackend, $scope, $rootScope, $q;
      var element;

      beforeEach(module('app'));
      beforeEach(function() {
        inject(function(_$compile_, _$http_, _$httpBackend_, _$rootScope_, _$q_) {
          $compile = _$compile_;
          $http = _$http_;
          $httpBackend = _$httpBackend_;
          $rootScope = _$rootScope_;
          $scope = $rootScope.$new();
          $q = _$q_;
        });

        $scope.data = "some data";
        element = $compile(angular.element('<my-dir info="data"></my-dir>'), $scope);
        $httpBackend.whenGET('path/to/template.html').respond('<div>test:{{someVal}}</div>');
      });

      it('should let me see its guts', function() {
        expect($scope.save).toBeTruthy();
        expect(typeof $scope.save).toBe("function");
      });

    });
     //]]>
  </script>


</head>

<body>

</body>

</html>

1

There are 1 answers

2
Abhishek Jain On BEST ANSWER

Here is an updated jsfiddle with your tests working with an inline directive template.

EDIT: Managed to get it work with external templates. I forgot to flush my http request. I have updated the full answer and you can see the updated jsfiddle here

There were a few problems with your test:-

  1. In the directive definition, you didn't give the directive name. The service myService wasn't injected in your directive, but as you hadn't defined your directive correctly, you were not getting that error.
  2. Also, you were right in trying to get the the scope of the element using element.isolateScope() earlier which you removed. This is because the directive will create a new isolated scope.
  3. You need to do $httpBackEnd.flush() to flush the pending request for external template. As you were not doing it, $compile in your test wasn't seeing the template and not compiling it correctly.

There are some more other minor changes that I did to make it work.

P.S. If you are using karma in your project, I use karma-ng-html2js-preprocessor for testing directives using external templates. Have a look here.

angular.module('app', []).directive('myDir', function () {

    return {
        restrict: 'E',
        //template: '<div>test:{{someVal}}</div>',
        templateUrl: 'path/to/template.html',
        scope: {
            info: "=info"
        },
        controller: function ($scope) {
            $scope.someVal = 'porcupine';
            $scope.save = function () { /* ... */
            };
        }
    }
});

describe('myDir', function () {

    var $compile, $scope, $rootScope;
    var element, isolatedScope;

    beforeEach(module('app'));
    beforeEach(inject(function (_$compile_, _$rootScope_, $httpBackend) {
        $httpBackend.whenGET('path/to/template.html').respond('<div>test:{{someVal}}</div>');

        $compile = _$compile_;
        $rootScope = _$rootScope_;
        $scope = $rootScope.$new();

        element = $compile('<my-dir info="data"></my-dir>')($scope);
        $httpBackend.flush();
        isolatedScope = element.isolateScope();
    }));

    it('should let me see its guts', function () {
        expect(isolatedScope.save).toBeTruthy();
        expect(typeof isolatedScope.save).toBe("function");
    });

});