Testing recipes for controllers
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
};
}));
...
});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
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.
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'
});
});
...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'
});
});
...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();
});
...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');
});
...