Unit Testing AngularJs using ngMock
We need to learn unit testing for TDD. Prior knowlege of angularjs and javascript is required. First question which comes into mind is why do TDD (Test Driven Development)? This means first write test cases and then develop your code to pass this particular test case. I think TDD is very important when writing clean, maintainable and concise code. TDD give you:
- Confidence in your code
- No duplicate code
- Maintains clean good quality
- No dead code. There are no functions or line of code which is not being used.
- These tests can be documentation for other developers maintaining your code. And much more.
Writing a Test Driven application requires:
- Writing a test
- Failing the test
- Write code to pass the test
- Run test again
This article is only related to writing tests for angularjs related code using ngMock. We would be using jasmine (Test Framework). Jasmin gives us following functionality:
- describe('Test Suite', function (){}) // For grouping of related tests
- it('Test name', function () {})
- beforeEach(), afterEach() //For running common section of group tests
- expect().toBe, toEqual, toThrow, not, toHaveBeenCalledWith // For Assertion
We would be testing below mentioned core angularjs principles using ngMock. ngMock is developed by angular team.
- Modules
- Controller
- Services
- Directives
- Filters
Essential ngMock Functions
We would be using angular.mock.module(alias, anonymousFunction, anonymousObject) and angular.mock.inject() for testing our code.
- angular.mock.module() - We would be using this to load our module(s) for testing purpose. Because angular.mock.module is available as a global object, we can simply use module()
- angular.mock.inject() - We would be using this to get instances of components (controller, services, filters, directives etc.) from modules for testing purpose. Because angular.mock.inject() is available as a global object, we can simply use inject()
Testing Controller
angular.mock.inject() provide us with $controller service which is a decorator for controller with additional binding parameters for getting controller to test it out.
$controller(constructor, // string or function
locals, // $scope object and other injectables
[bindings] //object for controllerAs binding
);
For example you want to test a controller
angular.module('testApp')
.controller('simpleController', function($scope, $location) {
$scope.search = function() {
if ($scope.query) {
$location.path('/results').search('q', $scope.query);
}
}
});
You would write something like this;
describe('Simple Controller', function() {
var $location;
var $scope;
beforeEach(angular.mock.module('testApp'));
beforeEach(angular.mock.inject(function(_$controller_, _$location_,) { //Using inject to get _$controller_ service
$scope = {};
$location = _$location_;
_$controller_('simpleController', { //sinple controller
$scope: $scope,
$location: _$location_
});
}));
it('should redirect to query results for non-empty query', function() {
$scope.query = 'Hello';
$scope.search();
expect($location.url()).toBe('/results?q=Hello');
});
it('should not redirect to query results for empty query', function() {
$scope.query = '';
$scope.search();
expect($location.url()).toBe('');
});
});
Testing Directives
We would be using compile() service given by angula.mock.inject() to compile an HTML string into template function which would be used to link scope with template. We would also be using $rootScope from angular.mock.inject() for scope. For example testing below directive
angular.module('testApp')
.directive('simpleResult', function() {
return {
restrict: 'E',
replace: true,
scope: {
result: '=result'
},
template: [
'<div class="row">',
'<h3>{{result.Title}}</h3>',
'</div>'
].join('')
};
});
We would do something like this:
describe('Simple Result Directive', function() {
var result = {
Title: 'Testing what is here'
};
var expectedHtml = [
'<div class="col-sm-4">',
'<h3 class="ng-binding">Testing what is here</h3>',
'</div>'
].join('')
var $compile;
var $rootScope;
beforeEach(module('testApp')); //loading Testing module
beforeEach(inject(function(_$compile_, _$rootScope_) { //$compie and $rootScope services
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should output result to expected HTML format', function() {
$rootScope.result = result;
var element = $compile('<simple-result result="result"></simple-result>')($rootScope);
$rootScope.$digest(); //For executing digest life cycle for angular
expect(element.html()).toBe(expectedHtml);
});
});
Testing Services with http calls to backend
For services testing they can be injected throught angular.mock.inject() with _ ExampleServiceName _ .
angular.module('dbCalls', [])
.factory('seachApi', function($http, $q) {
var service = {};
var baseUrl = 'http://someurl.com/?';
service.search = function(query) {
var deferred = $q.defer();
$http.get(baseUrl + 's=' + encodeURIComponent(query))
.success(function (data) {
deferred.resolve(data);
}).error(function (e) {
deferred.reject(e);
})
return deferred.promise;
}
return service;
});
You would write something like this:
describe('dbCalls service', function() {
var data = {"Search":[{"Title":"Testing what is here"}]};
var searchApi = {};
var $httpBackend;
var $exceptionHandler;
beforeEach(module('dbCalls'));
beforeEach(inject(function(_searchApi_, _$httpBackend_, _$exceptionHandler_) {
searchApi = _searchApi_; //Getting search Api service with this kind naming convension
$httpBackend = _$httpBackend_;//Using this service to prepare call for testing
$exceptionHandler = _$exceptionHandler_; //Using this service for handling exceptions
}));
it('should return search movie data', function() {
var response;
var expectedUrl = 'http://someurl.com/?&s=hello';
$httpBackend.when('GET', expectedUrl) // For testing purpose, mocking out response.
.respond(200, data);
searchApi.search('hello') //Calling services with
.then(function(data) {
response = data;
});
$httpBackend.flush(); //Run request as sync
expect(response).toEqual(data);
});
Testing Filters
angular.mock.inject() provide us with $filter service which is a decorator for filter for getting filter to test it out.
angular.module('testApp')
.filter('upperCase', function upperCaseFilter() {
return function (value) {
return value.toUpperCase();
};
});
// Unit test for this filter
describe('Upper Case Filter', function() {
var upperCase;
beforeEach(module('testApp'));
beforeEach(inject(function (_$filter_) {
upperCase = _$filter_('upperCase');
}));
it('should return upper case value for string', function() {
expect(upperCase('foo')).toBe('FOO');
});
});
With the knowledge of services provided by angular.mock() you can unit test anything related to angularjs. We looked into controller_, _compile, $filter, services to test controllers, directives, filters and services.
We should always write test cases but how much testing should be done is a debate for another day.