Testing

webTiger Logo Wide

Jasmine JS Unit Testing

The Jasmine JavaScript test framework is a simple to use solution for unit testing your heavily JavaScript-based Rich Internet Applications (RIA).

Initial Setup

First of all, if you haven’t already done so, download the latest Jasmine Release. Unzip it, preferably to a sub-folder in your project files structure.

Alternatively, for a slightly more complicated approach, you could install Node.JS (via its Windows Installer package) and then use NPM to install Jasmine by opening a Node.JS Command Prompt and typing the package installation command:

npm install -g generator-jasmineCode language: plaintext (plaintext)

(The -g option installs Jasmine into the Node.JS global modules folder. You can omit this option to install it in the folder the command prompt is pointed at instead.)

Either way, once we’ve downloaded Jasmine we need to configure it. Open the test runner HTML file in the root of the Jasmine folder (if you used the NPM approach and copied the files into the global modules folder then you might want to copy the Jasmine files to your project folder first.) Replace the script tag declarations relating to the source and spec files with ones for the JavaScript files you are testing. The original ones are for the example unit tests that ship with Jasmine, and we don’t need them for our own tests!

NOTE: If you are testing an AngularJS code-base, you’ll need to add angular-mocks.js AFTER the angular.js or angular.min.js script declaration or the unit tests won’t work (with the most likely error being: ReferenceError: ‘module’ is undefined.)

Writing Unit Tests

Jasmine uses a behavioural-driven development (BDD) approach.

You define one or more ‘suites’ of ‘specs’. Suites really just provide a means to group tests together and Specs are really the unit tests. Both Suites and Specs are defined as JavaScript functions (specs nested inside a suite.) The example below shows definitions of Jasmine suites (the second suite being defined is an example of injecting an AngularJS ‘controller’ into a Jasmine unit test):

describe('Suite Name', function() {
    it('Name of Spec (Test), or description for it', function() {
        /* Any test setup code you want! */
        var actual = true;
        var target = true;
        /* Evaluation */
        expect(target).toBe(actual);
    });
});

describe('Angular Test Example', function() {
    it('Test the Controller', inject(function($controller) {
        var scope = {};
        $controller('NameOfController', {$scope: scope});
        expect(scope.someProperty).toBe(24);
    }));
});Code language: JavaScript (javascript)

describe() is used to define a suite of tests (specs) and it() is used to define an individual test/spec within a suite. You can disable a suite of tests by renaming the describe function to ‘xdescribe’. Likewise, individual tests can be disabled by renaming them from it to ‘xit’.

The expect() function is used to evaluate data within a test. It accepts a single parameter – the expected entity being evaluated – and has evaluation extension functions (called matchers) that are used to evaluate the target entity against expected values. The following list is a non-exhaustive collection of the types of evaluation that can be performed:

  • expect(target).toBe(value) – equality matcher that compares using ===.
  • expect(target).not.toBe(value) – negation operator to allow not equal to, etc. (Works with most, if not all, matchers.)
  • expect(target).toEqual(value) – equality matcher for literals and variables.
  • expect(target).toMatch(/regex/) – regular expression matcher.
  • expect(target).toBeDefined() – checks if the target entity is defined.
  • expect(target).toBeUndefined() – checks if the target entity is undefined.
  • expect(target).toBeNull() – checks if the target entity is null.
  • expect(target).toBeTruthy() – performs a Boolean evaluation, checking the target entity is True.
  • expect(target).toBeFalsy() – performs a Boolean evaluation, checking the target entity is False.
  • expect(target).toContain(“something”) – checks for a value within an array (inc. a string).
  • expect(target).toBeLessThan(value) – checks the target is less than a particular value.
  • expect(target).toBeGreaterThan(value) – checks the target is greater than a particular value.
  • expect(target).toBeCloseTo(value, precision) – checks the target is close to a value, against a defined level of precision.
  • expect(target).toThrow() – checks a target throws an error when expected.
  • expect(target).toThrowError(error) – checks a target throws a specific error. The error parameter can be text, a type of error, a regular expression, etc.
  • fail(“reason for failing the test”) – allows a test to fail based on some extended criteria, in cases where expect() isn’t suitable (for example.)

You can also write your own custom matcher functions if the built-in ones aren’t suitable for your needs (in preference to using fail() all the time.)

Test suites can also include initialisation and clean-up code blocks. These are defined using beforeEach(), afterEach(), beforeAll() and afterAll() functions. As may be inferred by their names, the ‘each’ functions are called before or after each test in the suite executes, and the ‘all’ functions are called once before or after the entire suite of tests is run.

The example below uses beforeEach() to add a custom matcher that can be used in test evaluations:

var myMatchers = {
    toBeSomething: function() {
        return {
            compare: function(actual, expected) {
                var outcome = {};
                outcome.pass = someEvaluationWithBooleanOutcome(actual, expected);
                outcome.message = 'An informational message about the outcome.';
                return outcome;
            }
            /* (Optional) for cases where doing 
               expect(target).not.toBeSomething(value) won't work, you can 
               explicitly specify a negated comparer. */
            negativeCompare: function(actual, expected) {
                var outcome = {};
                outcome.pass = 
                    someNegatedEvaluationWithBooleanOutcome(actual, expected);
                outcome.message = 'An informational message about the outcome.';
                return outcome;
            }
        };
    }

    toBeSomethingElse: function() {
        /* Code omitted for brevity. */
    }
};Code language: JavaScript (javascript)

You can add the custom matcher(s) in the appropriate place in your test suite, like so:

describe('My Suite', function() {
    beforeEach(function() { jasmine.addMatchers(myMatchers); });
    it('My Test', function() {
        expect(target).toBeSomething(value);
    });
});Code language: JavaScript (javascript)

Other Capabilities

  • Spies – allows functions to be stubbed (mocked, so they aren’t called), to be tracked, to be replaced with alternative code, etc. For example, overriding normal ‘alert’ function that displays a modal prompt:
var mockAlert = { alert: jasmine.createSpy() };
module(function($provide) {
    $provide.value('$window', mock);
});Code language: JavaScript (javascript)
  • jasmine.clock – allows time-based code to be tested (e.g. by introducing additional delays, mock dates, etc.)
  • jasmine.* – there are various other jasmine.something functions (e.g. jasmine.createSpy(‘target’), jasmine.any(Object)
  • done – can be used with asynchronous activity to force a suite to wait until it completes before continuing with tests.