Bradley Braithwaite
1 import {
2 Learning,
3 JavaScript,
4 AngularJS
5 } from 'Brad on Code.com'
6 |
Check out my online course: AngularJS Unit Testing in-depth with ngMock.

Unit Testing $log in AngularJS - ngMock Fundamentals

How to unit test AngularJS code that uses the $log service with ngMock.

on
on javascript, angularjs, testing, ngmock

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:

log examples

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.

SHARE
Don't miss out on the free technical content:

Subscribe to Updates

CONNECT WITH BRADLEY

Bradley Braithwaite Software Blog Bradley Braithwaite is a software engineer who works for search engine start-ups. He is a published author at pluralsight.com. He writes about software development practices, JavaScript, AngularJS and Node.js via his website . Find out more about Brad. Find him via:
You might also like:
mean stack tutorial AngularJS Testing - Unit Testing Tutorials