bindToController in unit tests

In Angular 1.3 (see below for 1.4+)

Digging into the AngularJS source code I found an undocumented third argument to the $controller service called later (see $controller source).

If true, $controller() returns a Function with a property instance on which you can set properties.
When you’re ready to instantiate the controller, call the function and it’ll instantiate the controller with the properties available in the constructor.

Your example would work like this:

describe('buttons.RemoveButtonCtrl', function () {

  var ctrlFn, ctrl, $scope;

  beforeEach(inject(function ($rootScope, $controller) {
    scope = $rootScope.$new();

    ctrlFn = $controller('xxCtrl', {
      $scope: scope,
    }, true);
  }));

  it('should have a label', function () {
    ctrlFn.instance.label="foo"; // set the value

    // create controller instance
    ctrl = ctrlFn();

    // test
    expect(ctrl.label).toBe('foo');
  });

});

Here’s an updated Plunker (had to upgrade Angular to make it work, it’s 1.3.0-rc.4 now): http://plnkr.co/edit/tnLIyzZHKqPO6Tekd804?p=preview

Note that it’s probably not recommended to use it, to quote from the Angular source code:

Instantiate controller later: This machinery is used to create an
instance of the object before calling the controller’s constructor
itself.

This allows properties to be added to the controller before the
constructor is invoked. Primarily, this is used for isolate scope
bindings in $compile.

This feature is not intended for use by applications, and is thus not
documented publicly.

However the lack of a mechanism to test controllers with bindToController: true made me use it nevertheless.. maybe the Angular guys should consider making that flag public.

Under the hood it uses a temporary constructor, we could also write it ourselves I guess.
The advantage to your solution is that the constructor isn’t invoked twice, which could cause problems if the properties don’t have default values as in your example.

Angular 1.4+ (Update 2015-12-06):
The Angular team has added direct support for this in version 1.4.0. (See #9425)
You can just pass an object to the $controller function:

describe('buttons.RemoveButtonCtrl', function () {

  var ctrl, $scope;

  beforeEach(inject(function ($rootScope, $controller) {
    scope = $rootScope.$new();

    ctrl = $controller('xxCtrl', {
      $scope: scope,
    }, {
      label: 'foo'
    });
  }));

  it('should have a label', function () {
    expect(ctrl.label).toBe('foo');
  });
});

See also this blog post.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)