Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

Controller

Testing recipes for controllers

Table of contents

Boilerplate

Before start we need to initialize the related controller and mock it's dependencies.

describe('SampleController', function() {
    'use strict';

    var $rootScope,
        ctrl, scope;

    // load the related module before each spec runs
    beforeEach(module('moduleName'));

    // inject the $controller service to load the SampleController
    // and the $rootScope
    beforeEach(inject(function(_$rootScope_, _$controller_) {
        // save the injected $rootScope into a variable available across the whole file
        $rootScope = _$rootScope_;

        // create a brand new scope
        scope = $rootScope.$new();

        // load the SampleController mocking the $scope to the one created before
        ctrl = $controller('SampleController', {
            $scope: scope
        };
    }));

    ...
});

Watchers

Watches expressions are very commons in angularjs and your verification is very usefull. For validate our test, the code in controller is:

  ...
  $scope.$watch('baz', function(newVal) {
    if (newVal) {
      vm.baz = newVal + 'bar';
    }
  });
  ...

With watch created, our code coverage is:

  it('should have a watcher', function() {
    scope.baz = 'foo';
    scope.$apply();
    expect(ctrl.baz).toBe('foobar');
  });

For more information, please take a look in angularjs $watch official documentation

Working with events

Usually in AngularJS applications we can to create, work and manipulate events created via $on(). This method add a listener for event based in AngularJS scope.

But this events should be tested easily. For help in this job, Jasmine provide spies (spyOn() method), that accept many kinds of configuration.

$emit

This method uses Event Emitter async pattern and enable communication between components based in 1 x 1 communication via.

  // controllers.js
  ...
  function sendMessage() {
    $scope.$emit('sample:message', { foo: 'bar' });
  }
  ...

One way to test these events is based in this example.

  ...
  it('should emit an event', function() {
    spyOn(scope, '$emit');

    ctrl.sendMessage();
    expect(scope.$emit).toHaveBeenCalled();
    expect(scope.$emit).toHaveBeenCalledWith('sample:message', {
      foo: 'bar'
    });
  });
  ...

$broadcast

This method used Broadcast pattern. This pattern is very powerfull, because enable communication with all components that have a listener in this channel based in 1 x N.

  // controllers.js
  ...
  function broadcastEvent() {
    $scope.$broadcast('sample:broadcast', { foo: 'bar' });
  }
  ...

This test code example is very similar with $emit test, but your core implementation is very diferent. Let`s check the code test.

  // controllers.spec.js
  ...
  it('should broadcast an event', function() {
    spyOn(scope, '$broadcast');

    ctrl.broadcastEvent();
    expect(scope.$broadcast).toHaveBeenCalled();
    expect(scope.$broadcast).toHaveBeenCalledWith('sample:broadcast', {
      foo: 'bar'
    });
  });
  ...

$destroy

This event is very usefull for callbacks methods when the bind event is removed. For test it, spyOn method is used too.

  // controllers.js
  ...
  function doSomething() {}

  $scope.$on('$destroy', function() {
    vm.doSomething();
  });
  ...
  // controllers.spec.js
  ...
  it('should do something on $destroy', function() {
    spyOn(ctrl, 'doSomething');
    scope.$destroy();
    expect(ctrl.doSomething).toHaveBeenCalled();
  });
  ...

Best Practices

$apply instead $digest

Creating a new $scope and $compile your content, we wrap our changes to $scope using $apply to replace {{vm.foo}} with the final value and compile it. Doing it like this, it renders with the right context. We don’t need to call $digest separately as $apply internally calls $digest once finishes evaluating all changes. You should use $apply instead of $digest, because $digest is already invoked in $apply method internally.

  ...
  it('should have a watcher', function() {
    scope.baz = 'foo';
    scope.$apply();
    expect(ctrl.baz).toBe('foobar');
  });
  ...