0

I'm trying to retrieve the controller via $scope in my jasmine test, but fail miserably. Anybody know why?

When using the controllerAs syntax, the controller object is put on the $scope object using the name specified in controllerAs. So by running the code below in a browser using ng-app='MyApp' to bootstrap to angular, I can use chrome-dev tools to locate and select the directive element, and type $0.scope().myDirCtrl in the console. This does yield the controller object, so why can't I retrieve the controller object in my unit test?

Running the snippet below will kick off a standalone jasmine browser testing environment. The spec for the test is listed at the bottom of the snippet. The code I'm having issues with is this:

expect($scope.myDirCtrl).toBeDefined();

/* --------------------------------------
    Source code
   --------------------------------------*/
(function(angular) {
  'use strict';

  // Setup the template -----------------
  angular.module('MyApp.tpls', [])
  .run(['$templateCache', function($templateCache) {
    $templateCache.put('partials/myDirective.html',
                       '<div>{{myDirCtrl.testValue}}</div>');
  }]);

  // Setup the app ----------------------
  angular.module('MyApp', ['MyApp.tpls'])
    .directive('myDirective', myDirective)
    .controller('MyDirectiveController', MyDirectiveController);

  function myDirective() {
    return {
      restrict        : 'E',
      templateUrl     : 'partials/myDirective.html',
      transclude      : true,
      controllerAs    : 'myDirCtrl',
      bindToController: true,
      scope           : {},
      controller      : 'MyDirectiveController'
    };
  }

  MyDirectiveController.$inject = ['$scope'];
  function MyDirectiveController($scope) {
    var ctrl = this;
    ctrl.testValue = 'Only a test';
  }
})(angular);



/* --------------------------------------
    Test specifications
   --------------------------------------*/
(function (module) {
  'use strict';
  
  // Define the tests -------------------
  describe('My directive test', function () {
    var $compile, $rootScope, $scope;

    beforeEach(module('MyApp'));
    beforeEach(inject(function(_$compile_, _$rootScope_) {
      $compile   = _$compile_;
      $rootScope = _$rootScope_;
      $scope     = $rootScope.$new();
    }));

    it('scope should contain a controller reference', function () {
      var element = $compile(angular.element('<my-directive></my-directive>'))($scope);
      $scope.$digest();
      expect($scope.myDirCtrl).toBeDefined();
    });
  });
})(module);
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine-html.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/boot.min.js"></script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular-mocks.js"></script>

1 Answer 1

1

The problem is that $scope in your spec is not the same scope that is used in a MyDirectiveController. Directive creates one more scope for a controller, which becomes a child of your spec scope. You should be able to check it with the help of Angular internal properties in your test:

it('scope should contain a controller reference', function () {
    var element = $compile(angular.element('<my-directive></my-directive>'))($scope);
    $scope.$digest();

    // controller is actually in a child scope
    console.log($scope.$$childHead.myDirCtrl);

    expect($scope.myDirCtrl).toBeDefined();
});

But I would not suggest to rely on these private Angular properties like $$childHead, because they are considered private and are not the part of public API. I think this Q/A AngularJS - Access to child scope could help you to resolve this issue.

Sign up to request clarification or add additional context in comments.

3 Comments

You are right. expect(angular.element('> *', element).scope().myDirCtrl).toBeDefined(); yields true. Is this always true for directives, or is it something that's true only for a particular DDO setup? If the latter, what setting constitutes a need for creating a new childscope for housing the controller? transclue : true?
In your directive you have a property scope: {}. By defining this extra property you force a directive to create it's own a so called 'isolated' scope. (docs). If you remove it, it will inherit from a parent scope and test will pass.
Of course facepalm. I knew this actually, just didn't think of it in my unit test... How stupid of me. Thanks so much...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.