Controller Testing

Because AngularJS separates logic from the view layer, it keeps controllers easy to test. Let’s take a look at how we might test the controller below, which provides $scope.grade, which sets a property on the scope based on the length of the password.

angular.module(‘app’, [])

.controller(‘PasswordController’, function PasswordController($scope) {

$scope.password = ”;

$scope.grade = function() {

var size = $scope.password.length;

if (size > 8) {

$scope.strength = ‘strong’;

} else if (size > 3) {

$scope.strength = ‘medium’;

} else {

$scope.strength = ‘weak’;

}

};

});

Because controllers are not available on the global scope, we need to use angular.mock.inject to inject our controller first. The first step is to use the module function, which is provided by angular-mocks. This loads in the module it’s given, so it is available in your tests. We pass this into beforeEach, which is a function Jasmine provides that lets us run code before each test. Then we can use inject to access $controller, the service that is responsible for instantiating controllers.

describe(‘PasswordController’, function() {

beforeEach(module(‘app’));

var $controller, $rootScope;

beforeEach(inject(function(_$controller_, _$rootScope_){

// The injector unwraps the underscores (_) from around the parameter names when matching

$controller = _$controller_;

$rootScope = _$rootScope_;

}));

describe(‘$scope.grade’, function() {

it(‘sets the strength to “strong” if the password length is >8 chars’, function() {

var $scope = $rootScope.$new();

var controller = $controller(‘PasswordController’, { $scope: $scope });

$scope.password = ‘longerthaneightchars’;

$scope.grade();

expect($scope.strength).toEqual(‘strong’);

});

});

});

Notice how by nesting the describe calls and being descriptive when calling them with strings, the test is very clear. It documents exactly what it is testing, and at a glance you can quickly see what is happening. Now let’s add the test for when the password is less than three characters, which should see $scope.strength set to “weak”:

describe(‘PasswordController’, function() {

beforeEach(module(‘app’));

var $controller;

beforeEach(inject(function(_$controller_){

// The injector unwraps the underscores (_) from around the parameter names when matching

$controller = _$controller_;

}));

describe(‘$scope.grade’, function() {

it(‘sets the strength to “strong” if the password length is >8 chars’, function() {

var $scope = {};

var controller = $controller(‘PasswordController’, { $scope: $scope });

$scope.password = ‘longerthaneightchars’;

$scope.grade();

expect($scope.strength).toEqual(‘strong’);

});

it(‘sets the strength to “weak” if the password length <3 chars’, function() {

var $scope = {};

var controller = $controller(‘PasswordController’, { $scope: $scope });

$scope.password = ‘a’;

$scope.grade();

expect($scope.strength).toEqual(‘weak’);

});

});

});

Now we have two tests, but notice the duplication between the tests. Both have to create the $scope variable and create the controller. As we add new tests, this duplication is only going to get worse. Thankfully, Jasmine provides beforeEach, which lets us run a function before each individual test. Let’s see how that would tidy up our tests:

describe(‘PasswordController’, function() {

beforeEach(module(‘app’));

var $controller;

beforeEach(inject(function(_$controller_){

// The injector unwraps the underscores (_) from around the parameter names when matching

$controller = _$controller_;

}));

describe(‘$scope.grade’, function() {

var $scope, controller;

beforeEach(function() {

$scope = {};

controller = $controller(‘PasswordController’, { $scope: $scope });

});

it(‘sets the strength to “strong” if the password length is >8 chars’, function() {

$scope.password = ‘longerthaneightchars’;

$scope.grade();

expect($scope.strength).toEqual(‘strong’);

});

it(‘sets the strength to “weak” if the password length <3 chars’, function() {

$scope.password = ‘a’;

$scope.grade();

expect($scope.strength).toEqual(‘weak’);

});

});

});

We’ve moved the duplication out and into the beforeEach block. Each individual test now only contains the code specific to that test, and not code that is general across all tests. As you expand your tests, keep an eye out for locations where you can use beforeEach to tidy up tests. beforeEach isn’t the only function of this sort that Jasmine provides, and the documentation lists the others.

Testing Controllers with ‘Controller as’ Syntax

Testing a controller which uses the Controller as syntax is easier than testing the one using $scope. In this case, an instance of the controller plays the role of a model. Consequently, all actions and objects are available on this instance.

Consider the following controller:

angular.module(‘controllers’,[])

.controller(‘SecondController’, function(dataSvc){

var vm=this;

vm.saveData = function () {

dataSvc.save(vm.bookDetails).then(function(result) {

vm.bookDetails = {};

vm.bookForm.$setPristine();

});

};

vm.numberPattern = /^\d*$/;

});

The process of invoking this controller is similar to the process discussed earlier. The only difference is, we don’t need to create a $scope.

beforeEach(inject(function($controller){

secondController = $controller(‘SecondController’, {

dataSvc: mockDataSvc

});

}));

As all members and methods in the controller are added to this instance, we can access them using the instance reference.

The following snippet tests the numberPattern field added to the above controller:

it(‘should have set pattern to match numbers’, function(){

expect(secondController.numberPattern).toBeDefined();

expect(secondController.numberPattern.test(“100”)).toBe(true);

expect(secondController.numberPattern.test(“100aa”)).toBe(false);

});

Assertions of the saveData method remain the same. The only difference in this approach is with the way we initialize values to the bookDetails and bookForm objects.

The following snippet shows the spec:

it(‘should call save method on dataSvc on calling saveData’, function ()

secondController.bookDetails = {

bookId: 1,

name: “Mastering Web application development using AngularJS”,

author: “Peter and Pawel”

};

secondController.bookForm = {

$setPristine: jasmine.createSpy(‘$setPristine’)

};

passPromise = true;

secondController.saveData();

rootScope.$digest();

expect(mockDataSvc.save).toHaveBeenCalled();

expect(secondController.bookDetails).toEqual({});

expect(secondController.bookForm.$setPristine).toHaveBeenCalled();

});

Test Environment Setup
Testing Services and AJAX

Get industry recognized certification – Contact us

keyboard_arrow_up
Open chat
Need help?
Hello 👋
Can we help you?