Running Karma JS tests as part of a Maven CI build with Webjars

I spent several hours at work recently trying to solve this problem. The solution has a few moving parts to it, so I thought I’d jot down some notes to make it easier on myself and others in the future.

The problem

At my workplace we have an established Maven/Java workflow with tooling in place for executing CI builds and deploying built artefacts to our DEV/TEST/PROD environments etc. Some time ago we adopted Webjars as a way to manage static web resources via the Maven dependency management mechanism.

As we have become more front-end heavy and as the amount of business logic in our front-end code has increased, it has become increasingly important to integrate javascript unit and e2e tests into our build processes.

After evaluating some options we settled on the Karma test runner. This is a powerful test runner that supports cross-browser testing, multiple reporting formats and the ability to ‘watch’ project files and automatically re-run tests when a change is detected (very cool!). The easiest way to get Karma running is on NodeJS, which we didn’t have set up in our environment.

How do we integrate Karma-driven tests on a project that uses Webjar dependencies so that the tests can be run locally AND as part of a Maven CI build using our existing tooling?

If you want to skip ahead, a fully working project is available on github. Take a look at the README to get started.

Some background

First, some background on the various tools and libraries used for anyone not familiar with them.

Mavenhttp://maven.apache.org/
Maven is a dependency management and project build management system. It has become a de-facto standard for Java projects. It supports a wide variety of plugins that can be used together to perform arbitrarily complex build processes.

Webjars – http://webjars.org
Webjars is a convention for packaging client-side web libraries as JAR dependencies so they can be managed via Java-oriented tools such as Maven. They have the nice property that they can be included and managed as a project dependency in the same way that any regular JAR library can.

Karma – http://karma-runner.github.io/
Karma is a test runner library for executing JS tests. The tests can be run across multiple browsers and on multiple devices.

The solution

Overview

If you don’t want to get into the details, the breakdown of the solution is as follows:

  1. Setup webjar dependencies
  2. Host webjars using the Jetty Maven Plugin when developing locally
  3. Setup Karma to run locally using the hosted Webjar dependencies
  4. Use the Maven Dependency plugin to unpack Webjar dependencies during a CI build
  5. Create a CI-specific Karma configuration that uses the unpacked dependencies rather than the hosted ones during a CI build
  6. Use the Frontend-Maven-Plugin to install NodeJS and Karma local to the project during a CI build and execute the Karma tests
  7. (Optional) Add JUnit style reporting so the Karma test results can be reported on by CI tooling such as Jenkins etc.

Setup your project to use Webjars

This part is easy. Webjar dependencies can be added to your POM just as any other dependency.

<dependencies>
    ...
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>angularjs</artifactId>
        <version>1.2.16</version>
    </dependency>
    ...
</dependencies>

The Webjars documentation has instructions for getting Webjars running on a number of platforms. For ease of setup we’ll use a Servlet 3.0 container (see next section).

Setup Jetty configuration

The easiest way to make the webjar dependencies available is via a Servlet 3.0 container such as Jetty. I used the Jetty Maven Plugin which allows you to launch a fully configured Jetty container using Maven.

<build>
        <plugins>
            ...
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.2.0.M0</version>
                <configuration>
                    <webApp>
                        <contextPath>/maven-karma-webjars-example</contextPath>
                    </webApp>
                </configuration>
            </plugin>
            ...
        </plugins>
</build>

You can now launch Jetty using:

>> mvn jetty:run

And the webjar dependencies (along with any of your project resources) are now available on e.g. http://localhost:8080/maven-karma-webjars-example/webjars/angularjs/1.2.16/angular.min.js

Setup your Karma tests

Ideally you will be able to setup Karma to run locally on your development machine. Follow the installation instructions to get setup with NodeJS and Karma. If you can’t install Karma locally due to policy restrictions etc. you can get away without it – you will just need to execute tests via the Maven build which adds a bit of overhead.

The Karma setup instructions will get you most of the way there. Following Maven project conventions I put the karma.conf.js in src/test/webapp/js, which means you will need to set the project basePath appropriately. You will also want to reference the webjar dependencies at the container-hosted URL (e.g. http://localhost:8080/…).

module.exports = function(config) {
  config.set({
 
    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '../../../../',
 
    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],
 
    // list of files / patterns to load in the browser
    files : [
	'http://localhost:8080/maven-karma-webjars-example/webjars/angularjs/1.2.16/angular.js',
	'http://localhost:8080/maven-karma-webjars-example/webjars/angularjs/1.2.16/angular-mocks.js',
	'src/main/webapp/**/*.js', 
	'src/test/webapp/**/*.js' 
	],
 
 
    // list of files to exclude
    exclude: [
      '**/karma.*.js'
    ],
 
    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
 
    },
 
    // test results reporter to use
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],
 
    // web server port
    port: 9876,
 
    // enable / disable colors in the output (reporters and logs)
    colors: true,
 
    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,
 
    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,
 
    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],
 
    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};

Now, assuming you have installed the karma-cli, you can run the Karma tests using:

>> karma start src/test/webapp/js/karma.conf.js

and navigating to http://localhost:9876

Setup Karma tests to run in a CI build

The Karma tests now run locally and will helpfully watch for changes to project resources and re-run the tests (how good is that!). Unfortunately, they still require a container to host the webjars which is not really appropriate for a CI build. Your environment might support this setup on your build server, but a better solution is to remove the dependency on the container altogether during a CI build.

First, create a Karma config specific for your CI job, e.g. karma.ci.conf.js. In this config you will want to:

  1. Reference the primary Karma config file; and
  2. Override the config object to switch to single-run mode and stop watching files
//Load the base configuration
var baseConfig = require('./karma.conf.js');
 
module.exports = function(config) {
	// Load base config
	baseConfig(config);
 
	// Update the location of webjar dependencies
	updateWebjarPaths(config);
 
	// Override base config
	config.set({
 
		// Switch to a headless PhantomJS browser for tests
		browsers : [ 'PhantomJS' ],
 
		// Only run once in CI mode
		singleRun : true,
 
		// Do no watch for file changes
		autoWatch : false
	});
};

Now to unpack the webjar dependencies to the file system so that Karma can see them without using a container. Luckily, there is a Maven plugin available that can do just that.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <version>2.8</version>
  <executions>
    <execution>
      <id>unpack</id>
      <goals>
        <goal>unpack-dependencies</goal>
      </goals>
      <configuration>
        <includes>**/*.js</includes>
      </configuration>
    </execution>
  </executions>
</plugin>

The last thing left is to tell Karma how to resolve the unpacked dependencies. We could simply set the files property again with the new file locations, but this will result in duplication and we hate duplication. A better option is to take advantage of the fact that Karma config files are simply JavaScript and write a function to do the resolution for us:

...
module.exports = function(config) {
	// Load base config
	baseConfig(config);
 
	// Update the location of webjar dependencies
	updateWebjarPaths(config);
 
	// Override base config
	config.set({
           ...
        });
};
 
//Override path to webjar dependencies
function updateWebjarPaths(config) {
	for (var i = 0; i < config.files.length; i++){
		config.files[i] = config.files[i].replace(
		  'http://localhost:8080/maven-karma-webjars-example/webjars', 
		  'target/dependency/META-INF/resources/webjars');
	}
}

Karma is now ready to run in a CI build. We just need to make Maven execute them at the correct time.

Invoke Karma from Maven

Karma runs on NodeJS. A simple way to get the tests running from Maven would be to execute the Karma CLI from the Maven build. However, this would require installing Node on your build server which can be difficult in some environments.

That’s where the Frontend Maven Plugin comes in. This neat plugin will install NodeJS local to the project, run NPM to install required dependencies, and then execute Karma.

<plugin>
	<groupId>com.github.eirslett</groupId>
	<artifactId>frontend-maven-plugin</artifactId>
	<version>0.0.14</version>
	<executions>
 
		<!-- 1. Install node and npm locally -->
		<execution>
			<id>install node and npm</id>
			<goals>
				<goal>install-node-and-npm</goal>
			</goals>
			<configuration>
				<nodeVersion>v0.10.18</nodeVersion>
				<npmVersion>1.3.8</npmVersion>
			</configuration>
		</execution>
 
		<!-- 2. Install karma and karma plugins -->
		<execution>
			<id>npm install</id>
			<goals>
				<goal>npm</goal>
			</goals>
		</execution>
 
		<!-- 3. Run karma tests -->
		<execution>
			<id>javascript tests</id>
			<goals>
				<goal>karma</goal>
			</goals>
			<configuration>
				<karmaConfPath>src/test/webapp/js/karma.ci.conf.js</karmaConfPath>
			</configuration>
		</execution>
	</executions>
</plugin>

Now give your config a whirl.

>> mvn clean install

If everything is working your tests should be run during the test phase of your build.

(Optional) Add a JUnit reporter

A little added extra that I find particularly useful is to configure a JUnit reporter for Karma that outputs test results in the JUnit XML format. This can be read along with your regular Java unit test results by your CI server to give aggregated test results that include your JS tests.

See https://github.com/karma-runner/karma-junit-reporter for more details on how to set it up.

Summary

At the end of the (somewhat lengthy) process above we now have:

  • Karma tests that can be run locally with Webjar dependencies
  • A Karma configuration suitable for running on a CI server; and
  • A Maven configuration that can setup Node+NPM+Karma as part of the build and execute the Karma tests

A working example project that illustrates everything above is available on github. Feel free to clone it and experiment with different configurations that make sense for your environment.

There are still some rough edges with this configuration that I am ironing out as development continues. These include:

  1. You need to remember to run the Jetty server before you try to execute your local Karma tests for the first time. If you don’t you get a less-than-helpful error.
  2. There are some issues with using Node/NPM and the Frontend Maven Plugin behind a corporate proxy server that has caused some problems for us. I haven’t gone into details here, but some workarounds have been needed to make it work nicely.

If you have thoughts or comments please post below. I’d really like to hear if/how other people have solved this (possibly arcane) problem.

8 thoughts on “Running Karma JS tests as part of a Maven CI build with Webjars

  1. http://radiobelgie.be/print.php?type=F&thread=1283&post=1286&nr=1

    Hey I know this іs off topic but I was
    wondering if үou knew of ɑny widgets I coulɗ add to my
    blog that automatically tweet my newest twitter updates.
    І’ve been loοking for a plug-in like this for quite sοme time and was hoping mɑybe yоu ѡould
    have some experience with ѕomething like this. Рlease let mе кnoԝ if you run into anything.
    І truly enjoy reading ʏour blog and I look forward to
    your new updates.

    Reply
  2. www m88

    I truly do enjoy writing but it just seems like the first 10 to 15 minutes tend to be lost just trying to figure out how to begin. Any suggestions or hints? Thank you!

    Reply
  3. Sherman

    You could definitely see your skills in the work you write.
    The worrld hopes for evgen morre passionate writers such ass you who arre not afraid too ssay how they believe.
    Always follow your heart.

    Reply
  4. livecodes

    Wow, marvelous weblog layout! How long have you
    been blogging for? you ake blogging look easy.
    The overall look of your website is fantastic, as well as
    the content!

    Reply

Leave a Reply to www minecraft net Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>