Use Karma and Grunt to Run Your Jasmine Tests in Real-Time


As JavaScript applications become more common and more complex, the need for good unit test coverage also increases. Hopefully you’re already writing tests. If not, why not?

When I’m doing TDD with C#, I use NCrunch to monitor all tests within the Visual Studio Solution and run them as they change. This saves me having to manually run unit tests every now and again and gives me fast notifications if anything I’m writing has broken existing functionality.

NCrunch doesn’t support Jasmine unit tests so we need to find another way to handle those.

Counter

To begin with, we’ll need some code to test with. So we’ll use a simple Counter class to write tests against. In the spirit of TDD, we’ll write tests first for add, subtract and difference calculations and then add in an implementation to make the tests pass.

This is the folder structure we’ll be using:

projectfolder
- source
  - counter.js
  - counter.tests.js
- runner
  Test runner files will go here

counter.tests.js

/// <reference path="counter.js">
'use strict';
 
describe('Counter tests', function () {
  it('Add gives the correct result', function () {
    // Arrange
    var num1 = 1;
    var num2 = 3;
    var expected = 4;
 
    // Act
    var result = counter.add(num1, num2);
 
    // Assert
    expect(result).toBe(expected);
  });
 
  it('Subtract gives the correct result', function () {
    // Arrange
    var num1 = 1;
    var num2 = 3;
    var expected = -2;
 
    // Act
    var result = counter.subtract(num1, num2);
 
    // Assert
    expect(result).toBe(expected);
  });
 
  it('Difference gives the correct result when first number is larger', function () {
    // Arrange
    var num1 = 5;
    var num2 = 3;
    var expected = 2;
 
    // Act
    var result = counter.difference(num1, num2);
 
    // Assert
    expect(result).toBe(expected);
  });
 
  it('Difference gives the correct result when second number is larger', function () {
    // Arrange
    var num1 = 3;
    var num2 = 5;
    var expected = 2;
 
    // Act
    var result = counter.difference(num1, num2);
 
    // Assert
    expect(result).toBe(expected);
  });
 
  it('Difference gives zero when both numbers are the same', function () {
    for (var i = 0; i <= 100; i++) {
      // Arrange
      var expected = 0;
 
      // Act
      var result = counter.difference(i, i);
 
      // Assert
      expect(result).toBe(expected);
    }
  });
});

Everything in there should be pretty straight-forward. The first reference line is to point to our implementation so that ReSharper can pick up and run the tests through Visual Studio if needed. It doesn’t effect anything we’re doing here.

Now we have our tests, we can write our code file.

counter.js

'use strict';
 
var counter = (function () {})();

There’s currently no implementation in there, which is what we want. Before we can trust any of our tests, we need to see them fail.

How to Sell a Contradiction

karma

Karma is a JavaScript test runner created by the AngularJS team. From the documentation:

Karma is essentially a tool which spawns a web server that executes source code against test code for each of the browsers connected. The results for each test against each browser are examined and displayed via the command line to the developer such that they can see which browsers and tests passed or failed.

Karma also watches all the files, specified within the configuration file, and whenever any file changes, it triggers the test run by sending a signal the testing server to inform all of the captured browsers to run the test code again. Each browser then loads the source files inside an IFrame, executes the tests and reports the results back to the server.

The watch functionality of Karma is going to allow us to leave it running in the background, watching for changes in any files we specify then re-running tests.

To set up Karma, we need a config file. For our requirements, it’ll look like this:

karma.conf.js

module.exports = function (config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '../source/',
 
    // frameworks to use
    frameworks: ['jasmine'],
 
    // list of files / patterns to load in the browser
    files: ['**/*.js'],
 
    // list of files to exclude
    exclude: [],
 
    // test results reporter to use
    reporters: ['progress'],
 
    // web server port
    port: 9876,
 
    // enable / disable colors in the output (reporters and logs)
    colors: true,
 
    // level of logging
    logLevel: config.LOG_INFO,
 
    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,
 
    // Start these browsers
    browsers: ['PhantomJS'],
 
    // If browser does not capture in given timeout [ms], kill it
    captureTimeout: 60000,
 
    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false,
  });
};

Note the basePath and files settings which are pointing at the directory containing our test file and the (still to be written) implementation.

To run the Karma tests, we need to install all dependencies and then run it. We’ll handle this using npm and Grunt.

Doing the Heavy Lifting For You

grunt

Grunt is a task runner that lets you write functionality in JavaScript and then have it run as needed. So, anything you’d like to automate, this will let you do it. It’s a very good fit for our automated testing needs.

Grunt tasks are just JavaScript files. Any additional functionality can be loaded in from libraries via an npm package.json file. The Karma team already have a Grunt task for use so we can use our package file to load this as well as Grunt, Karma and additional Karma runners. This looks like:

package.json

{
  "name": "karma-through-grunt",
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo 'Error: no test specified' && exit 1"
  },
  "author": "Kevin Wilson",
  "devDependencies": {
    "karma-script-launcher": "~0.1.0",
    "karma-chrome-launcher": "~0.1.2",
    "karma-firefox-launcher": "~0.1.3",
    "karma-ie-launcher": "~0.1",
    "karma-jasmine": "~0.1.5",
    "karma-phantomjs-launcher": "~0.1.2",
    "karma": "~0.10.9",
    "karma-story-reporter": "~0.2.2",
    "grunt-karma": "~0.6.2",
    "grunt-cli": "~0.1.13",
    "karma-sauce-launcher": "~0.1.8"
  }
}

If you want any more information on these packages, you can look them up on the npm module directory but most should be self-explanatory.

The Grunt file itself is simple:

gruntfile.js

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
 
    karma: {
      unit: {
        configFile: 'karma.conf.js',
      },
    },
  });
 
  grunt.loadNpmTasks('grunt-karma');
  grunt.registerTask('default', ['karma']);
};

All we’re doing here is telling Grunt to load all packages from the package.json file then setting up a single Karma task, the settings from which are to be taken from the karma.conf.js file.

Running it All

That’s quite a bit of configuration without running anything, so let’s fire it up and see what we get.

By now, your directory structure should look like this:

projectfolder
- source
  - counter.js
  - counter.tests.js
- runner
  - gruntfile.js
  - karma.conf.js
  - package.json

If you don’t have Node.js installed, you’re not going to get very far so go and install that from http://nodejs.org/.

Once that’s done, open up the npm command prompt and cd into your runner folder within the project:

cd projects/jsblog/jasmine-karma/runner

Install the required Node.js packages:

npm install

This will pull down all dependencies from the npm directory. Once that’s done (it could take a minute or two and you’ll get a lot of output on the screen), you can run your Grunt file:

grunt

If all goes well, your Grunt file will run your tests and you’ll get output something like this:

grunt-all-fail

Here you can see that the 5 tests within our counter.tests.js file have been run and all fail (as expected). The runner hasn’t finished executing though as it’s now monitoring the file system for changes. We can start adding in functionality to our file and seeing how the tests update.

Open up your counter.js file and add an implementation for the add and substract methods:

'use strict';
 
var counter = (function () {
  var add = function (num1, num2) {
    return num1 + num2;
  };
 
  var subtract = function (num1, num2) {
    return num1 - num2;
  };
 
  return {
    add: add,
    subtract: subtract,
  };
})();

When you save your changes, your command line runner should run all tests again and update the results to:

grunt-3-fail

Here you can see that only 3 out of the 5 tests are now failing.

Now add in the implementation for the difference method:

'use strict';
 
var counter = (function () {
  var add = function (num1, num2) {
    return num1 + num2;
  };
 
  var subtract = function (num1, num2) {
    return num1 - num2;
  };
 
  var difference = function (num1, num2) {
    if (num1 > num2) {
      return num1 - num2;
    }
 
    return num2 - num1;
  };
 
  return {
    add: add,
    subtract: subtract,
    difference: difference,
  };
})();

Save your changes again and all tests should now be passing:

grunt-all-pass

Moving On

Now that you’ve gotten this running, you can add more browsers into the test suite via Karma. Just update the browsers property of karma.conf.js.

You could also set the test runner to run on your continuous integration server using online browser testing suites. Colin Gemmel has a good write up on this using Gulp and SauceLabs browsers.

Source Code

All source for this example is available on [GitHub](https://github.com/kwilson/karma-through-grunt).