Randomness
in Code

Testing Node on the Server and Angular on the Client

January 28, 2015

One of the most useful features of Angular, in my opinion, is the focus on testing your code. Structuring your apps to use dependency injection by default, along with an out-of-the-box solution for unit and integration tests, gives anyone a great head start in testing their client code. Using a similar solution on the server side, and wiring it all together with a tool like Grunt, provides you with complete testing of your web application.

The examples below are mostly taken from the meanjs-template, which takes advantage of the MEAN.JS stack. Additionally, the colors application uses the same test frameworks, but does so with just plain Node, Express, and Angular. Please feel free to use either to gain more information on the topic.

Server Side: Jasmine Unit Tests

There's an ongoing debate (as with most anything in the JavaScript world) on which framework to use for unit testing your server side code. For me it came down to Mocha or Jasmine. I actually prefer the flexibility Mocha provides when paired with an assertion library such as Chai, but because Jasmine is used (by default) on the client side, it felt natural to use the same framework on the server side.

To use Jasmine within the Node environment, I chose to use the popular jasmine-node module. Another very useful module is rewire which allows you to access "private" functions and variables from within your source modules. So for instance, if you would like to test a function which is not explicitly exported, you can still gain access to the function in this way:

var hiddenFunction = myModule.__get__("hiddenFunction");

Example Test

I usually create a folder within my project named test, and within that, a directory for the server side tests. From there the directory structure mirrors the source code which it tests. In the meanjs-template project I have a sample logger.js module within the app/lib directory. Correspondingly, in the test/server directory I have a logger.test.js file in the same app/lib directory.

Testing the logic using Jasmine then becomes fairly simple, now that I can get access to both the exported and "private" functions. For example, here's a snippet of logger.test.js:

"use strict";

var rewire = require("rewire");

describe("The logger library", function() {
    var logger;

    beforeEach(function() {
        logger = rewire("../../../../app/lib/logger");
    });

    it("should exist", function() {
        expect(logger).toBeDefined();
    });

    it("should return test decorator in test environment", function() {
        var _getDecorator;

        _getDecorator = logger.__get__("_getDecorator");

        expect(_getDecorator()).toEqual("meanjs-template:test_environment");
    });

    // ...
});

Client Side: Karma Unit Tests

Using Angular on the client side somewhat simplifies our choices for frameworks, as they provide great integration with Karma for unit tests. Karma (by default) is setup to use Jasmine as it's test framework and is configured via it's karma.conf.js file.

karma.conf.js

That configuration is pretty straightforward, and for the most part you'll only need to supply Karma with the files you want to test, and how you want to test them. Below is an example karma.conf.js file from the meanjs-template I've built, and even though I take advantage of the MEAN.JS configuration for the files, the concept is still the same:

"use strict";

var applicationConfiguration = require("./config/config");

module.exports = function(config) {
    config.set({

        files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),

        autoWatch: true,

        frameworks: ["jasmine"],

        browsers: ["Chrome", "Firefox"],

        plugins: [
            "karma-chrome-launcher",
            "karma-firefox-launcher",
            "karma-jasmine"
        ]
    });
};

In this file I'm configuring Karma to load both the source JavaScript files (including the 3rd party libraries) as well as the test JavaScript files. (Note that the files attribute expects an array of file names.) Additionally, Karma is configured to watch for any file changes, use the Jasmine test framework, and run it's tests through both the Chrome and Firefox browsers.

Example Test

In the same way as the server side tests, I create a test/client directory to house my client side tests. Within that directory I create a unit directory to contain the unit tests. From there the directory structure mirrors the client side source code.

So for example, I have an Angular controller called app.client.controller.js that lives in the global/controllers directory. So on the test side I then have a global/controllers directory within the test/unit folder, and a file named app.client.controller.test.js within that folder structure to test the controller.

It's important when testing Angular controllers to load the module prior to injecting the controller into your test. You also are responsible for creating the dependencies for the controller and injecting them in prior to running your tests. This has the benefit of giving you complete control over the dependencies, which allows you to easily test error conditions or create a situation for any test. Keeping to our example, this is the contents of the app.client.controller.test.js file contained within the meanjs-template:

"use strict";

describe("AppCtrl", function() {
    var $scope, controller;

    beforeEach(module(ApplicationConfiguration.applicationModuleName));

    beforeEach(inject(function($rootScope, $controller) {
        // create a new $scope for each test
        $scope = $rootScope.$new();

        // use the new $scope in creating the controller
        controller = $controller("AppCtrl", {
            $scope: $scope
        });

    }));

    it("should exist", function() {
        expect(controller).toBeDefined();
    });
});

This test first loads the root module, loading the Angular application. It then uses the inject function to provide us with a $rootScope and $controller (benefits of using the Karma framework). We can use these to create a $scope and pass that in when loading our AppCtrl.

Our one test simply checks that the controller is defined, but you can imagine additional tests as the logic for your controller grows. Check out the Karma documentation for more information on how to use Karma to test your source code.

Client Side: Protractor End to End Tests

Angular's integration with Protractor to provide end to end tests can be extremely powerful. Using this, in addition to the unit tests of your server and client side code, provides a set of tests that truly validate your source code's functionality.

If you're not familiar with Protractor, it provides some additional tooling around Selenium's WebDriver, which allows you to automate an actual interaction with the browser (typing the keys to fill out a form, clicking buttons, etc). See the Protractor API for more information on what's available.

protractor-conf.js

Protractor has a configuration file which you use to state where your test files are located, what browsers will be used during testing, the test framework, etc. Similar to the unit tests, I generally place my end to end tests within the test/client/e2e directory. Below is an example protractor-conf.js file:

"use strict";

exports.config = {
    allScriptsTimeout: 11000,

    specs: [
        "test/client/e2e/*.js"
    ],

    multiCapabilities: [{
        browserName: "firefox"
    }, {
        browserName: "chrome"
    }],

    baseUrl: "http://localhost:3000/",

    beforeLaunch: function() {
        console.log("Starting setup...");

        // Return a promise when dealing with asynchronous
        // functions here (like creating users in the database)
    },

    afterLaunch: function() {
        console.log("Starting cleanup...");

        // Return a promise when dealing with asynchronous
        // functions here (like removing users from the database)
    },

    framework: "jasmine",

    jasmineNodeOpts: {
        defaultTimeoutInterval: 30000
    }
};

This file takes advantage of the multiCapabilities configuration attribute to state that the tests should run in both Firefox and Chrome. There is also beforeLaunch and afterLaunch functions defined which provide us a hook for any test setup or tear down (for example, create a test user to use during login).

Another key configuration attribute is the baseUrl which states where Protractor will begin it's tests. Note that we've supplied http://localhost:3000, which means we'll need to start our web server prior to running the Protractor tests.

Example Test

The Protractor tutorial provides a great overview on how to write tests which I recommend looking over. Below is an example test used in the same meanjs-template:

"use strict";

describe("meanjs-template App", function() {
    beforeEach(function() {
        // get the root of the project
        browser.get("/");
    });

    it("should display heading", function(done) {
        browser.getTitle().then(function(title) {
            expect(title).toEqual("meanjs-template");
            done();
        });
    });
});

This test navigates to the root of the project, then verifies the page title is correct. Protractor provides many additional functions to get elements on the page using Angular specific concepts, such as by binding or by model.

Using Grunt to Wire it All Together

Now that we have all three of the test groups setup, the difficulty comes in running them all together. A build system like Grunt helps solve this problem by enabling us to run these groups of tests, one after another, resulting in full validation of our application. (In addition to the tests above, we can also add in jshint along with csslint to validate our source code semantics.)

View the getting started guide for Grunt to get your project initially setup. The three Grunt plugins I chose to use to run the tests are grunt-jasmine-node, grunt-karma, and grunt-protractor-runner. With all of that installed, the Gruntfile.js for our tests becomes:

"use strict";

module.exports = function(grunt) {
    grunt.initConfig({
        jasmine_node: {
            options: {
                forceExit: true,
                matchall: true,
                showColors: true,
                includeStackTrace: true
            },
            all: ["test/server"]
        },
        karma: {
            all: {
                configFile: "karma.conf.js",
                singleRun: true
            }
        },
        protractor: {
            all: {
                configFile: "protractor-conf.js",
                keepAlive: true
            }
        }
    });

    require("load-grunt-tasks")(grunt);

    grunt.registerTask("server", "Start the server", function() {
        require("./server.js");
    });

    grunt.registerTask("test", ["jasmine_node", "karma", "server", "protractor"]);
};

The above configuration file specifies the options for Jasmine, Karma, and Protractor (mostly default options), loads all the necessary Grunt tasks, and defines two of our own tasks: one to start the server, and one which will run our tests. We'll need to start the server prior to running the Protractor tests, since Protractor is a true end to end test harness.

With this Gruntfile.js in place, executing the tests from the command line can by done with the following command:

$ grunt test

This will run the server side unit tests with Jasmine, the client side unit tests with Karma, and the end to end tests with Protractor. Pretty cool stuff!

Check out the colors application for additional examples, or the meanjs-template for examples on how to use this with continuous integration, like Jenkins or Travis CI.