Unit Testing Guidelines

A unit tests has 3 goals that it should accomplish to test a javascript object:

  • Checks success, error, and edge cases
  • Tests as few objects as possible
  • Demonstrates how an object should be used

With those 3 goals in mind, its important to realize that the variety of AngularJS object types means that the same approach won’t work for each and every object. Since the OpenLMIS-UI coding conventions layout patterns for different types of AngularJS objects, it’s also possible to illustrate how to unit test objects that follow those conventions.

Check out AngularJS’s unit testing guide, its well written and many of out tests follow their styles.

Here are some general rules to keep in mind while writing any unit tests:

  • Keep beforeEach statements short and to the point, which will help other’s read your statements
  • Understand how to use Spies in Jasmine, they can help isolate objects and provide test cases

Defining variables

The version of Jasmine we’re using discourages using the define-block scoped variables as they might be causing memory leaks. In order to prevent that, it is suggested to use ‘this’ as a way of sharing variables between beforeEach, afterEach, inject and it blocks. Keep in mind that closures inside those block will have a different context and thus ‘this’ will refer to a different object. Below are two examples on how to and how to not write unit tests for OpenLMIS.

Do

describe('CustomResource', function() {

    beforeEach(function() {
        module('custom');

        inject(function($injector) {
            this.subjectUnderTest = $injector.get('subjectUnderTest');
            this.$rootScope = $injector.get('$rootScope');
        });

        this.expected = 'expectedString';
    });

    describe('returnSomething', function() {

        beforeEach(function() {
            this.subjectUnderTest.prepareForTest();
        });

        it('should return something', function() {
            var result;
            this.subjectUnderTest.returnSomething()
                .then(function(something) {
                    result = something;
                    //this.subjectUnderTest won't be available as we have a different context here
                });
            this.$rootScope.$apply();

            expect(result).toEqual(this.expected)
        });

    });

    afterEach(function() {
        this.subjectUnderTest.clearAfterTest();
    });

});

Don’t

describe('CustomResource', function() {

    var expected, subjectUnderTest, $rootScope;

    beforeEach(function() {
        module('custom');

        inject(function($injector) {
            subjectUnderTest = $injector.get('subjectUnderTest');
            $rootScope = $injector.get('$rootScope');
        });

        expected = 'expectedString';
    });

    describe('returnSomething', function() {

        beforeEach(function() {
            subjectUnderTest.prepareForTest();
        });

        it('should return something', function() {
            var result;
            subjectUnderTest.returnSomething()
                .then(function(something) {
                    result = something;
                });
            $rootScope.$apply();

            expect(result).toEqual(expected)
        });

    });

    afterEach(function() {
        this.subjectUnderTest.clearAfterTest();
    });

});