Managing JavaScript Test Coverage with SonarQube, Maven, Karma and gulp

SonarQube (formerly Sonar) has become a standard code quality tool for Java applications. With recent releases, it has become easier to analyse code other than Java, even all within the same project. This means that you can now bring code quality analysis to your JavaScript code, too. In this blog post, we show you how.

Overview

Our project is built with Maven. To manage our frontend build process, we use gulp via the frontend-maven-plugin.

We configure the Karma test runner to output JUnit and LCOV reports. From gulp, we run Karma to execute our JavaScript tests, and then do a little post-processing on the LCOV report so that SonarQube can understand it.

We run SonarQube itself as part of our continuous integration process with Jenkins. Our Jenkins job configures SonarQube with which JavaScript files to include and exclude (we don’t want to analyse third-party libraries, for example) and paths to the coverage reports.

Prerequisites

We make the following assumptions:

  • You are already using Maven to build your project.
  • You already have a SonarQube instance set up.
  • You already have a continuous integration server such as Jenkins set up, with the appropriate SonarQube CI plugin installed.
  • You are already testing your JavaScript code. The test framework you choose (Jasmine, Mocha, etc.) is up to you.

No particular folder structure is required, but you will probably be working with something similar to the below.

Directory Structure
Example Directory Structure

Ok? Then let’s get started!

Node and npm

gulp and Karma are both based on Node.js, so we need to install Node to run them. We will use the package manager npm to install Node packages. (Note: after we are done, the frontend-maven-plugin will take care of this process for others working on our project, but to set everything up in the first place, it is easier if we install Node and npm ourselves.)

Head over to the Node.js downloads page and download the installer for your system. Run the installer to install Node together with npm.

Now we need to prepare our project to use it with npm. In essence, this just means creating a package.json file. This file holds metadata related to the project, including its dependencies. Later, other developers can install all the required packages from this file with the npm install command. We can create a more detailed package.json file through the interactive prompt npm init, but as name and version are the only required fields, you can start by just creating the file based on the following contents:

{
  "name": "frontend-project",
  "version": "0.0.1"
}

Done? Great! Now we can install Karma. From the root of your frontend project, execute the following at the command line.

npm install --save-dev karma

You should also install packages and plugins for your test framework at this point. For Mocha with Chai and Sinon, for example:

npm install --save-dev mocha karma-mocha karma-sinon-chai

We also want to go ahead and install the required plugins for generating our unit test and coverage reports:

npm install --save-dev karma-coverage karma-junit-reporter

The --save-dev flag tells npm to save the name and version of each downloaded package to the devDependencies section of package.json. Have a look at your package.json file now. You should find the above packages listed there along with a semantic version number.

package.json with devDependencies
package.json with devDependencies

You’ll also have noticed by this point that you have a new directory called node_modules where all your Node packages live. You should exclude this directory from version control.

After installing npm packages
After installing npm packages

Karma

Now that Karma is installed, we need to configure it. It’s a good idea to create two config files for Karma: one which is used in conjunction with Karma’s watch mode for Test-Driven Development, and another which is based upon this but is modified slightly for use with Continuous Integration.

Karma offers an interactive option for generating configuration files:

./node_modules/karma/bin/karma init

Follow the prompts to configure Karma for TDD in your project. Make sure you get your test framework(s) in there.

When you are finished, Karma will tell you where it has saved the configuration file. Open karma.conf.js and have a look at the configuration. It contains comments that explain each setting.

If the config file is not where you would like it, you can move it and tell Karma the new location later (just adjust the basePath setting if you do so). We’re going to move ours under src/test/js/ (changing the value of basePath to '../../..').

If you haven’t already done so, now is also a good time to make sure you add your Karma config files to the excludes section of karma.conf.js. As our CI config file will be called karma.conf.ci.js, use the wildcard character to capture both files, e.g. src/test/js/karma.conf*.js.

Now, create a new file in the same directory called karma.conf.ci.js. Add the following contents.

var baseConfig = require('./karma.conf.js');

module.exports = function (config) {
    // Load base config
    baseConfig(config);

    // Override base config
    config.set({
        singleRun: true,
        colors:    false,
        autoWatch: false,
        reporters: ['progress', 'junit', 'coverage'],
        preprocessors:    {
            'src/main/webapp/resources/js/**/*.js':   ['coverage']
        },
        browsers:  ['PhantomJS'],
        junitReporter: {
            outputFile: 'reports/junit/TESTS-xunit.xml'
        },
        coverageReporter: {
            type:   'lcov',
            dir:    'reports',
            subdir: 'coverage'
        }
    });
};

Here we are telling Karma to run just once, without autoWatch, and without colours in the output (which sometimes cause problems in continuous integration). Moving down, we specify that we want to use the JUnit and Coverage reporters alongside the standard Progress reporter. Further, we add preprocessing of our JavaScript source files for the Coverage reporter, and restrict the browsers used to the headless PhantomJS only. Finally, we specify that the JUnit test report should be named TESTS-xunit.xml and output under reports/junit while the LCOV report should go under reports/coverage.

The name of the JUnit test report is important: this naming is required for SonarQube to recognise the report as being of type xUnit. For the reports, you can create whatever directory structure you want. It is a good idea to exclude the directories from version control, however.

Karma configuration files
Karma configuration files

At this point, you should be able to execute Karma from the command line to run your tests and generate your JUnit and LCOV reports. (This is definitely rather unwieldy, but fear not, we won’t be running Karma this way for long!)

./node_modules/karma/bin/karma start src/test/js/karma.conf.ci.js

Reports there? Great!

Reports
Reports

There is just one problem: the paths generated in the LCOV report are wrong. They begin with the current directory (SF:.) where they should instead name the project folder, e.g. SF:frontend-project. We can fix this pretty easily with gulp though, which is what we’ll do next.

LCOV path
LCOV path

gulp

gulp is a very powerful streaming build system based on Node.js. It can transform all aspects of your frontend build process, but here we will focus on using it to run our JavaScript tests and generate coverage reports with Karma.

Install gulp via:

npm install --save-dev gulp

To use gulp, you need to create a gulpfile.js in the root of your project. Create the file and add the following code to it.

var gulp = require('gulp');
var karma = require('karma').server;

gulp.task('test', function () {
    karma.start({
        configFile: __dirname + '/src/test/js/karma.conf.ci.js'
    });
});

This describes a gulp task called test, which will start the Karma server, run the tests according to the configuration in src/test/js/karma.conf.ci.js, and stop. This is exactly what we did when we ran Karma from the command line in the previous section.

So, all going well, running ./node_modules/gulp/bin/gulp.js test will run our tests and produce our reports in the expected directories. Try it out now!

Now we can move on and fix our path problem by creating another gulp task to perform some post-processing on the report with help from the gulp-replace plugin.

Install the plugin via

npm install --save-dev gulp-replace

and then update your gulpfile to define gulp-replace and a task to do the post-processing, and then call the post-processing task as a callback after we are finished running Karma.

var gulp = require('gulp');
var karma = require('karma').server;
var replace = require('gulp-replace');

var postprocessLCOV = function() {
    return gulp.src('reports/coverage/lcov.info')
        .pipe(replace('SF:.', 'SF:frontend-project'))
        .pipe(gulp.dest('reports/coverage'));
};

gulp.task('test', function () {
    karma.start({
        configFile: __dirname + '/src/test/js/karma.conf.ci.js'
    }, postprocessLCOV);
});

Running ./node_modules/gulp/bin/gulp.js test now should result in a LCOV report that SonarQube can understand.

Maven

Now, this is all very well for running our tests from the command line, but how do we integrate this into our Maven workflow? This is where the frontend-maven-plugin comes in.

Instead of trying to manage the frontend build process itself, the frontend-maven-plugin gets out of the way and lets a more suitable tool do the work. The plugin has been around for about a year, starting out with Karma and Grunt. Back in January I contributed the support for gulp.

To use this plugin, we first need to add it to our Maven project. Add the following code to the pom.xml in your frontend project.

<plugins>
    <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>0.0.16</version>
        ...
    </plugin>
    ...
</plugins>

Now we can configure the plugin to automatically install Node and npm and then run our gulp test command.

Install Node and npm

The following execution installs Node and npm locally on the target system. Here you can specify the versions of Node and npm to install. (See the README for more details.)

<execution>
    <id>install node and npm</id>
    <goals>
        <goal>install-node-and-npm</goal>
    </goals>
    <configuration>
        <nodeVersion>v0.10.32</nodeVersion>
        <npmVersion>1.4.28</npmVersion>
    </configuration>
</execution>

Install dependencies

The following execution runs npm install to install all the dependencies listed in your package.json. (See the README for more options.)

<execution>
    <id>npm install</id>
    <goals>
        <goal>npm</goal>
    </goals>
</execution>

Run gulp

The following execution runs the gulp test command during the test phase. (See the README for more options.)

<execution>
    <id>gulp build</id>
    <goals>
        <goal>gulp</goal>
    </goals>
    <phase>test</phase>
    <configuration>
        <arguments>test</arguments>
    </configuration>
</execution>

Running mvn clean install should now install Node and npm (if required), install all the required packages via npm install and then run Karma and the report post-processing via gulp test.

Note: since the frontend-maven-plugin installs Node and npm locally to a node directory, you should also exclude this directory from version control.

SonarQube

Create a SonarQube project for your frontend project.

Jenkins

If not already existing, create a Maven-based Jenkins job for your frontend project, and set the main build step to do a mvn clean install.

As a Post Step, choose “Invoke Standalone Sonar Analysis”. Select your SonarQube instance for the “Sonar Installation”, and add the following to the “Project properties” section (adjusting the values for your project and its corresponding SonarQube project). Further properties can be found in the SonarQube documentation.

Note: it is also possible to define the SonarQube properties within your pom.xml.

# required metadata
sonar.projectKey=frontend-project
sonar.projectName=My-Frontend-Project
sonar.projectVersion=1.0.0-SNAPSHOT

# path to source directories (required)
sonar.sources=frontend-project/src/main/webapp
sonar.tests=frontend-project/src/test/js

# excludes
sonar.exclusions=frontend-project/src/main/webapp/js/lib/**/*

# coverage reporting
sonar.javascript.lcov.reportPath=frontend-project/reports/coverage/lcov.info
sonar.surefire.reportsPath=frontend-project/reports/junit

This tells Jenkins everything it needs to know about the integration with SonarQube.

Note: by not setting a sonar.language option, it defaults to “multi-language”. This means that HTML, CSS and so on will also be analysed by SonarQube. If this is not desired, set the language option explicitly to JavaScript:

sonar.language=js

Run the Jenkins job, and voilà! You should now see the “Unit Tests Coverage” widget on your SonarQube project page.

Unit Tests Coverage Widget
Unit Tests Coverage Widget

Happy analysing and enjoy watching your coverage numbers climb!

4 thoughts on “Managing JavaScript Test Coverage with SonarQube, Maven, Karma and gulp

  1. Were you ever able to have the unit test results and not just the coverage? It seems Sonar only support surefire output and I couldn’t find a way to have karma output in that format.

    1. Hi Simon,

      Sonar actually removed the ability to import execution reports[1] for a while. They did come to their senses and bring it back[2], but it relies on version 2.2+ of the JavaScript plugin, which in turn requires SonarQube 4.5.1+.

      Alternatively, you could try the Generic Coverage Plugin[3], setting the path to your reports with the sonar.genericcoverage.unitTestReportPaths property key, but I haven’t tested that.

      As you say, doing all of the above won’t help you unless your reports are in the right format. There are a couple more plugins that can help you with that, but none specifically for gulp + Karma. There is one for Mocha though[4], and one for grunt + Karma[5], which you could maybe adapt?

      Sorry for the late reply, hope this helps.

      [1] http://jira.sonarsource.com/browse/SONARJS-163
      [2] http://jira.sonarsource.com/browse/SONARJS-239
      [3] http://docs.sonarqube.org/display/PLUG/Generic+Test+Coverage
      [4] https://www.npmjs.com/package/mocha-sonar-reporter
      [5] https://www.npmjs.com/package/grunt-karma-sonar

  2. Alice – thanks for writing this. It helped me a lot. I spent a lot of time trying to get the karma-junit-reporter output into SonarQube. Ultimately, I decided to write my own SonarQube plugin to use the xml file that is output from karma-junit-reporter as-is, without using gulp to attempt to modify it. The plugin will populate the unit test results and not just the coverage as Simon is asking for in his comment above.

    Since this seemed like a common problem, I decided to make my plugin available as an open source project. Here is the link to it in case anybody else runs into this problem and doesn’t want to spend 2 days trying to solve it like I did 🙂

    https://github.com/acwatson/sonar-karma-test-report-plugin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s