AngularJS Karma testing

Last year I was involved in making a prototype web app using AngularJS, and as it there was the possibility that it may end up as the foundation to production code we wrote unit tests for it’s internal functionality. Whilst there is a ton of examples for testing AngularJS apps we still ran into a few issues so I’m going to try to expand on some bits of setup.

Karma config and fixtures

If you want to load any fixtures Karma must be aware of the files though it’s config;

files: [
  {
    pattern: 'test/fixtures/**/*.*',
    watched: true,
    included: false,
    served: true
  }
]

This allows you to import these through Jasmine;

// given relative path test/fixtures/ to karma
var path = '';
if (typeof window.__karma__ !== 'undefined') {
  path += 'base/';
}
jasmine.getFixtures().fixturesPath = path + 'test/fixtures';
jasmine.getJSONFixtures().fixturesPath = path + 'test/fixtures';

Change the response of a mocked service

When mocking your services it’s common to directly call ‘respond’ after whenGet (or similar method). If instead you hold onto the result of the whenGet call you can change it’s response at a later stage, allowing you to easily test your fail conditions.

beforeEach(inject(function($injector) {
  // Set up the mock http service responses
  $httpBackend = $injector.get('$httpBackend');

  var responseMock = window.getJSONFixture('service-export.json');

  submissionResponder = $httpBackend.whenGET(/^\/api\/export*/);
  submissionResponder
      .respond(responseMock);

}));

Mock AngularJS cookies

Whilst the code itself is straight forward it’s important to note that the ngCookies mock does not set real cookies.

beforeEach(angular.mock.module('ngCookies', 'yourApp'));

// Set an activeUser cookie
beforeEach(inject(function ($cookieStore) {
  $cookieStore.put('activeUser', { id: 1 });
}));

Mock Google Maps (or any other external library)

beforeEach(module('yourApp', function ($provide) {
  var mockMapApi = {
    get : function() {
      return {
        then : function (callback) {
          //apply the given callback to a variable in the current scope so we can test it's result?
          //suppliedCallback = callback;
        }
      };
    }
  };
  
  $provide.value('mapApi', mockMapApi);
}));

//mock the Google Map service
beforeEach(inject(function ($window) {
  var mockGoogle = {
    maps : {
      Map : function () {},
      Marker : function (markerParam) {
        markerValue = markerParam;
      },
      Animation : { DROP : '' }
    }
  };
  $window.google = mockGoogle;
  
}));

Test a nested Controller

beforeEach(inject(function ($rootScope, $controller) {
  //setup the parent scope
  topScope = $rootScope.$new();
  $controller('AppCtrl', {
    $scope: topScope
  });

  //create controller scope
  scope = topScope.$new();

  thisCtrl = $controller('InnerCtrl', {
    $scope: scope
  });
}));

Mock a parent Controller or Directive

Rather than trying to test a nested directive by compiling the full HTML it’s simpler to mock the parent directives

beforeEach(inject(function ($rootScope, $compile) {
  var elementHTML = jasmine.getFixtures().read('source.html');
  parentScope = $rootScope.$new();
  //fake the parent directive
  parentScope.payments = {
    fromaccount : fromAccountId,
    toaccount : ''
  };
  parentScope.isTransfer = false;

  scope = parentScope.$new();

  element = angular.element(elementHTML);
  element = $compile(element)(scope);

}));
Filed under: JavaScript