Unit Testing $log in AngularJS - ngMock Fundamentals
How to unit test AngularJS code that uses the $log service with ngMock.
on
In this post we take a look at unit testing with ngMock’s $log service. This provides a replacement of Angular’s implementation of $log.
When would we use Angular’s $log service? It’s a simple service for logging, so this would be preferred over using console.log directly. It also includes logic for older browsers, where the console.log function may not exist and therefore prevents an error.
Standard Methods
The service has the following standard functions for logging messages at appropriate levels:
Function | Description |
---|---|
log(); | Write a log message. |
info(); | Write an information message. |
warn(); | Write a warning message. |
error(); | Write an error message. |
debug(); | Write a debug message. |
The service uses the $logProvider for configuration, which allows us to toggle the debug function, this setting is true by default. We would use this if we wanted to include debug trace messages throughout our code, but switch them off in production code.
Here’s an example application demonstrating basic usage:
var app = angular.module('calculatorApp', []).config(function($logProvider) {
// you can configure the debug option
$logProvider.debugEnabled(false);
});
app.controller('CalculatorController', function calculatorController($scope, $log) {
// We could also use:
// $log.log('standard log');
// $log.info('info log');
// $log.error('error log');
// $log.warn('warn log');
// $log.debug('some debug information');
$scope.sum = function sum() {
$log.debug('start of sum');
$scope.result = $scope.x + $scope.y;
$log.debug('the result is ' + $scope.result);
$log.debug('end of sum');
}
});
Each log message looks as follows in Chrome’s JavaScript console:
The Mock Implementation
The ngMock version of $logProvider is used in place of angular’s version. If you refer to ngMock’s source code, you will find the following snippet where this mock version is registered:
angular.module('ngMock', ['ng']).provider({
// ngMock's version of LogProvider is used in place of angular's
$log: angular.mock.$LogProvider
});
The mock implementation includes the previously described functions, and catches each call to the log functions and stores them in an array, so that we can assert the values at each level (error, warn, debug etc) in our tests.
It adds two additional methods specifically for testing:
Function | Description |
---|---|
reset(); | To empty the array at each level (debug, warn, error etc). |
assertEmpty(); | Used to check that nothing has been logged i.e. it will fail a test if our code under test has had something logged at any level. |
Example Tests
Asserting the value(s) of messages logged can be done as follows:
it('should write to debug log when calling sum', function() {
var productsController = $controller('CalculatorController', { $scope: $scope });
$scope.x = 1;
$scope.y = 2;
$scope.sum();
// We could replace accordingly, e.g.
// $log.error.logs
// $log.warn.logs
expect($log.debug.logs[0]).toEqual(['start of sum']);
expect($log.debug.logs[1]).toEqual(['the result is 3']);
expect($log.debug.logs[2]).toEqual(['end of sum']);
// alternative method of checking
expect($log.debug.logs).toContain(['the result is 3']);
});
We can make use of the assertEmpty function to verify that nothing was logged e.g. if we wanted to check an error condition did not occur:
it('should not call log (using reset)', function() {
var productsController = $controller('CalculatorController', { $scope: $scope });
$scope.x = 1;
$scope.y = 2;
$scope.sum();
// this clears the logs
$log.reset();
expect($log.assertEmpty).not.toThrow();
});
If there is a requirement to configure the debugging level within tests, this could be done as follows:
describe('logs with debugging disabled', function () {
beforeEach(module(function($logProvider) {
// We can configure the debugging level (the default is true)
$logProvider.debugEnabled(false);
}));
beforeEach(inject(function(_$controller_, _$log_) {
$controller = _$controller_;
$log = _$log_;
$scope = {};
}));
it('should not write to log when calling sum', function() {
var productsController = $controller('CalculatorController', { $scope: $scope });
$scope.x = 1;
$scope.y = 2;
$scope.sum();
expect($log.assertEmpty).not.toThrow();
});
});
Example Test Code
Full code example of the tests used in this post via a Github Gist.