AngularJS Testing in Nutshell

Tags:  javascript, angular

In this post I will talk about AngularJS testing. This will be quick and easy guide to get up and running with AngularJS testing. This post cover basics of :


Karma #


Jasmine #

Jasmine provides the function to group the tests together.

describe('Description for group of tests', function(){
    
    // individual tests go here
    
});

In Jasmine we create a spec(test) by calling it function.

it('This is description for test', function(){
    
    // Test code go here
    
});

So basically our test looks like below.

describe('some description', function(){
    
    it('some descrption', function(){
        
        //test code go here
        
    });
    
});

Testing Controller #

Let us assume that we have controller like below

 angular
        .module('appName', [])

        .controller('MainCtrl', function(MainService) {
            var vm = this;

            vm.someText = 'Initial Text';
            vm.serviceReturnedValue = MainService.callMe();

            vm.changeText = function() {
                vm.someText = 'Text Changed';
            };
        });

In order to test this simple controller we need to

STEP 1. Load the module using angular.mock.module('appName'); or module('appName');

STEP 2. Get the hold of $controller service using inject function.

var $controller;
inject(function(_$controller_) {
    $controller = _$controller_;
});
// _ (underscore) is used if you want to cache $controller service with same name.

STEP 3. Initialize the controller using $controller service.


// $controller('ControllerName', dependenciesObject);

var MainService = {
    callMe: function() {
        return 'Hello World';
    }
}; // notice how we are simply mocking services

var MainCtrl = $controller('MainCtrl', {MainService: MainService});

STEP 4. Make assertion


expect(MainCtrl.someText).toBe('Initial Text');
expect(MainCtrl.someText).not.toBe('Random Text');

expect(MainCtrl.serviceReturnedValue).toBe('Hello World');
expect(MainCtrl.serviceReturnedValue).not.toBe('Random World');

MainCtrl.changeText();
expect(MainCtrl.someText).not.toBe('Initial Text');
expect(MainCtrl.someText).toBe('Text Changed');

More on Jasmine Expectation here.


Testing Services #

Suppose we have a service that makes AJAX calls, which looks like

.factory('MainService', function($resource) {
    return $resource('/api/:id');
})

To unit test this component we need to make sure we do not mock the actual service MainService we are trying to mock. We also need to make sure we do not make an actual call to external resources. So we need to somehow mock the http requests itself. Luckily, we have $httpBackend service which will help us mock http requests. Let’s see how we can achieve this

STEP 1 . Load the module with module('appName');

STEP 2 . Get hold of $httpBackend and MainService. Notice we don’t mock MainService here.


var $httpBackend;
var MainService;
inject(function(_$httpBackend_, _MainService_) {
    $httpBackend = _$httpBackend_;
    MainService = _MainService_;
});

STEP 3 . Mock request that we will be making using $httpBackend.


$httpBackend
    .expectGET('/api/1')
    .respond( 200, { name: 'Aman', id: 1 } );

Notice we are using expectGET method here to expect the GET request to be made and when we get one we tell our mock request to respond with status 200 and return an object. Like expectGET we have expectPOST, expectDELETE … etc.

STEP 4 . Make a http request and make it synchronous using $httpBackend.flush()

var result;
MainService.get({id: 1}, function(data) {
    result = data;
});

$httpBackend.flush();

STEP 5 . Make Expectation


expect(result.name).toBe('Aman');
expect(result.name).not.toBe('Random Text');

expect(result.id).toBe(1);
expect(result.id).not.toBe(5);


Testing Directive #

// simple directive
.directive('myDirective', function() {
    return function(scope, elem) {
        elem.append('<span>This is appended element</span>');
    };
});

// OR

.directive('myDirective', function() {
    return {
        template: '<span>This is appended element</span>'
    };
});

Let’s see how we can test this directive.

STEP 1 Load the module. module('appName');

STEP 2 Get hold of $compile service and $rootScope

var $compile, $rootScope;
inject(function(_$compile_, _$rootScope_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;
});

STEP 3 Compile the directive

var directiveElem = getCompiledElement();

function getCompiledElement() {
    var element = angular.element('<div my-directive/></div>');
    var compiledElement = $compile(element)($rootScope);
    $rootScope.$digest();
    return compiledElement;
}

For compiling the directive we are first creating the element that has our directive. Then we are using $compile servie to compile our direcive For completing hte compile process we start the digest cycle Then at end we return the compiled element.

STEP 4 Make Expectation

var spanElement = directiveElem.find('span');
expect(spanElement).toBeDefined();
expect(spanElement.text()).toEqual('This is appended element');
expect(spanElement.text()).not.toEqual('Some random text');


Testing Directive with templateUrl #

Testing a directive with template referred from a file is tricky, as the directive makes an $httpBackend request to the templateUrl. Adding this template to $templateCache makes the task of testing easier and the template will be easy to share. This can be done using the karma-ng-html2js-preprocessor.

This preprocessor converts HTML files into JS strings and generates Angular modules. These modules, when loaded, puts these HTML files into the $templateCache and therefore Angular won’t try to fetch them from the server.


preprocessors: {
    'path/to/html/template': ['ng-html2js'],
},

ngHtml2JsPreprocessor: {
    moduleName: 'Templates'
}

Just make sure you load Templates module when you test your directive.


Bonus - To run single test on Karma using IntelliJ Idea #

Update #

If you update your IntelliJ IDEA and Karma plugin to latest version you can run individual test directly from UI (Like we do in Java test).

STEP 1 Install Karma plugin

STEP 2 Open karma.conf.js and you will get the run configuration. Run it.

STEP 3 All the test will run the first time. Then after completion open tab Karma Server.

STEP 4 Notice server is still running. Open the terminal and run following command karma run path/to/conf/file/karma.conf.js -- --grep=description

For example above we use karma run path/to/karma.conf.js -- --grep=helloWorld because helloWorld is describe description.