Unit Testing with AngularJS: Testing Other Components

All the tests I have shown you so far have been for a controller, but as the previous chapters have shown, there are several types of components in an AngularJS application. In the sections that follow, I demonstrate how to write a simple unit test for each of them.

1. Testing a Filter

You can obtain instances of a filter through the $filter service, which I introduced in Chapter 14. In Listing 25-12, you can see how I have added a filter to the app.js file.

Listing 25-12. Adding a Filter to the app.js File

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope, $http, $interval, $timeout, $log) {

$scope.intervalCounter = 0;

$scope.timerCounter = 0;

$interval(function () {

$scope.intervalCounter++;

}, 5, 10);

$timeout(function () {

$scope.timerCounter++;

}, 5);

$http.get(“productData.json”).success(function (data) {

$scope.products = data;

$log.log(“There are ” + data.length + ” items”);

});

$scope.counter = 0;

$scope.incrementCounter = function() {

$scope.counter++;

}

})

.filter(“labelCase”, function () {

return function (value, reverse) {

if (angular.isString(value)) {

var intermediate = reverse ? value.toUpperCase() : value.toLowerCase();

return (reverse ? intermediate[0].toLowerCase() :

intermediate[o].toUpperCase()) + intermediate.substr(l);

} else {

return value;

}

};

});

This is a custom filter that I created in Chapter 14. Listing 25-13 shows the contents of the tests/filterTest.js file that I created to test the filter.

Listing 25-13. The Contents of the filterTest.js File

describe(“Filter Tests”, function () {

var filterInstance;

beforeEach(angular.mock.module(“exampleApp”));

beforeEach(angular.mock.inject(function ($filter) {

filterInstance = $filter(“labelCase”);

}));

it(“Changes case”, function () {

var result = filterInstance(“test phrase”);

expect(result).toEqual(“Test phrase”);

});

it(“Reverse case”, function () {

var result = filterInstance(“test phrase”, true);

expect(result).toEqual(“tEST PHRASE”);

});

});

I use the inject method to obtain an instance of the $filter service and use it to obtain an instance of the filter, which I assign to a variable called filterInstance. I obtain the filter object within a beforeEach function, which means that I get a fresh instance for each test.

2. Testing a Directive

Testing a directive is a little more complicated because of the way that directives are applied to, and can modify, HTML. This means unit tests for directives rely on jqLite and the $compile service, which I described in Chapters 15 and 19, respectively. Listing 25-14 shows how I added a directive to the app.js file.

Listing 25-14. Adding a Directive to the app.js File

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope, $http, $interval, $timeout, $log) {

$scope.intervalCounter = 0;

$scope.timerCounter = 0;

$interval(function () {

$scope.intervalCounter++;

}, 5, 10);

$timeout(function () {

$scope.timerCounter++;

}, 5);

$http.get(“productData.json”).success(function (data) {

$scope.products = data;

$log.log(“There are ” + data.length + ” items”);

});

$scope.counter = 0;

$scope.incrementCounter = function () {

$scope.counter++;

}

})

.filter(“labelCase”, function () {

return function (value, reverse) {

if (angular.isString(value)) {

var intermediate = reverse ? value.toUpperCase() : value.toLowerCase();

return (reverse ? intermediate[0].toLowerCase() :

intermediate[0].toUpperCase()) + intermediate.substr(1);

} else {

return value;

}

};

})

.directive(“unorderedList”, function () {

return function (scope, element, attrs) {

var data = scope[attrs[“unorderedList”]];

if (angular.isArray(data)) {

var listElem = angular.element(“<ul>”);

element.append(listElem);

for (var i = 0; i < data.length; i++) {

listElem.append(angular.element(‘<li>’).text(data[i].name));

}

}

}

});

The directive is one that I created in Chapter 15. It uses an array of values taken from the scope and uses them to generate an unordered list. Listing 25-15 shows the contents of the tests/directiveTest.js file, which shows how to test the directive.

Listing 25-15. The Contents of the directiveTest.js File

describe(“Directive Tests”, function () {

var mockScope; var compileService;

beforeEach(angular.mock.module(“exampleApp”));

beforeEach(angular.mock.inject(function($rootScope, $compile) {

mockScope = $rootScope.$new();

compileService = $compile;

mockScope.data = [

{ name: “Apples”, category: “Fruit”, price: 1.20, expiry: 10 },

{ name: “Bananas”, category: “Fruit”, price: 2.42, expiry: 7 },

{ name: “Pears”, category: “Fruit”, price: 2.02, expiry: 6 }];

}));

it(“Generates list elements”, function () {

var compileFn = compileService(“<div unordered-list=’data’></div>”);

var elem = compileFn(mockScope);

expect(elem.children(“ul”).length).toEqual(1);

expect(elem.find(“li”).length).toEqual(3);

expect(elem.find(“li”).eq(0).text()).toEqual(“Apples”);

expect(elem.find(“li”).eq(1).text()).toEqual(“Bananas”);

expect(elem.find(“li”).eq(2).text()).toEqual(“Pears”);

});

});

I use the inject method to obtain the $rootScope and $compile services. I create a new scope and assign the data that the directive will use to the data property. I keep a reference to the $compile service so that I can use it in the test.

Following the approach that I described in Chapter 19, I compile a fragment of HTML to which the directive has been applied, specifying that the source of the data is the scope data array. This produces a function that I invoked with the mock scope to get the HTML output from the directive. To assess the results, I use jqLite to check the structure and the order of the elements that the directive has produced.

3. Testing a Service

Obtaining an instance of a service to test is easy because the inject method can be used, just as I have been doing to get the built-in and mocked services in earlier tests. In Listing 25-16, I have added a simple service to the app.js file.

Listing 25-16. Adding a Service to the app.js File

angular.module(“exampleApp”, [])

.controller(“defaultCtrl”, function ($scope, $http, $interval, $timeout, $log) {

$scope.intervalCounter = 0;

$scope.timerCounter = 0;

$interval(function () {

$scope.intervalCounter++;

}, 5, 10);

$timeout(function () {

$scope.timerCounter++;

}, 5);

$http.get(“productData.json”).success(function (data) {

$scope.products = data;

$log.log(“There are ” + data.length + ” items”);

});

$scope.incrementCounter = function () {

$scope.counter++;

}

})

.filter(“labelCase”, function () {

return function (value, reverse) {

if (angular.isString(value)) {

var intermediate = reverse ? value.toUpperCase() : value.toLowerCase();

return (reverse ? intermediate[0].toLowerCase() :

intermediate[0].toUpperCase()) + intermediate.substr(1);

} else {

return value;

}

};

})

.directive(“unorderedList”, function () {

return function (scope, element, attrs) {

var data = scope[attrs[“unorderedList”]];

if (angular.isArray(data)) {

var listElem = angular.element(“<ul>”);

element.append(listElem);

for (var i = 0; i < data.length; i++) {

listElem.append(angular.element(‘<li>’).text(data[i].name));

}

}

}

})

.factory(“counterService”, function () {

var counter = 0; return {

incrementCounter: function () {

counter++;

},

getCounter: function() { return counter;

}

}

});

I used the factory method, described in Chapter 18, to define a service that maintains a counter and that defines methods that increment and return the counter value. This isn’t a useful service in its own right, but it lets me demonstrate the process for testing a service. Listing 25-17 shows the contents of the tests/serviceTest.js file.

Listing 25-17. The Contents of the serviceTest.js File

describe(“Service Tests”, function () {

beforeEach(angular.mock.module(“exampleApp”));

it(“Increments the counter”, function () {

angular.mock.inject(function (counterService) {

expect(counterService.getCounter()).toEqual(0);

counterService.incrementCounter();

expect(counterService.getCounter()).toEqual(1);

});

});

});

Just for some variety, I use the inject function to obtain the service object within the Jasmine it function. I then test the counter value, increment it, and then test again. The tools that AngularJS provides for unit testing are heavily oriented toward instantiating services, which make them simple and easy to test.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *