Karma: Managing Plugin Versions in package.json

PROBLEM

Let’s assume our package.json looks like this:-

{
  "name": "testKarma",
  "private": true,
  "devDependencies": {
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.4",
    "karma-coverage": "^0.2.4",
    "karma-jasmine": "^0.2.2",
    "karma-junit-reporter": "^0.2.1",
    "karma-phantomjs-launcher": "^0.1.4"
  }
}

What we want to do is to update all the plugin versions defined in this file.

SOLUTION

After trying out several solutions, it appears that using npm-check-updates is a better and cleaner solution for discovering newer versions of these plugins.

First, we need to install npm-check-updates globally. You may need to use sudo to do so.

sudo npm install -g npm-check-updates

Once installed, run the following command within the project root directory to discover any new versions:-

npm-check-updates

Output:-

"karma-chrome-launcher" can be updated from ^0.1.4 to ^0.1.5 (Installed: 0.1.5, Latest: 0.1.5)
"karma-coverage" can be updated from ^0.2.4 to ^0.2.6 (Installed: 0.2.6, Latest: 0.2.6)
"karma-jasmine" can be updated from ^0.2.2 to ^0.2.3 (Installed: 0.2.3, Latest: 0.2.3)
"karma-junit-reporter" can be updated from ^0.2.1 to ^0.2.2 (Installed: 0.2.2, Latest: 0.2.2)

Run 'npm-check-updates -u' to upgrade your package.json automatically

Finally, run the following command to update the plugins:-

npm-check-updates -u

Output:-

"karma-chrome-launcher" can be updated from ^0.1.4 to ^0.1.5 (Installed: 0.1.5, Latest: 0.1.5)
"karma-coverage" can be updated from ^0.2.4 to ^0.2.6 (Installed: 0.2.6, Latest: 0.2.6)
"karma-jasmine" can be updated from ^0.2.2 to ^0.2.3 (Installed: 0.2.3, Latest: 0.2.3)
"karma-junit-reporter" can be updated from ^0.2.1 to ^0.2.2 (Installed: 0.2.2, Latest: 0.2.2)

package.json upgraded

The plugin versions are successfully updated, and package.json is also updated accordingly.

{
  "name": "testKarma",
  "private": true,
  "devDependencies": {
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.5",
    "karma-coverage": "^0.2.6",
    "karma-jasmine": "^0.2.3",
    "karma-junit-reporter": "^0.2.2",
    "karma-phantomjs-launcher": "^0.1.4"
  }
}

Why I Am Switching to Karma

OVERVIEW

JavaScript testing is hard. Most of the time, it is just plain difficult to set up the test harness just to run Javascript tests.

CURRENT STATE

Most of my team’s existing production web applications do not use any MV* frameworks. A few newer projects use Backbone/Marionette.

We rely on the following stack:-

We choose this direction because these Maven plugins provide good integration with Jenkins.

What is Wrong with the Current State

FUTURE STATE

We will now rely on the following stack:-

Why This is a Better State

  • Developed by Google developers, primary test harness for AngularJS.
  • Allows us to use Jasmine 2.x.
  • Test framework agnostic. Although we are using Jasmine now, we can easily switch to Mocha in the future.
  • MV* framework agnostic. It works with any MV* or homegrown framework.
  • Great integration with Jenkins.
  • Great integration with IntelliJ.
  • Ability to run tests on different browsers at the same time, such as Chrome, Firefox, PhantomJS, etc.

Jenkins: Getting Karma Generated Test Results to Appear in Maven Project Job

PROBLEM

Jenkins, for some reason, does not pick up Karma generated JUnit test reports even though they are created in the right directory… and apparently, it is a known problem. While Freestyle project job allows us to manually publish these JUnit reports, my intention is to rely on Maven project job to do the same thing.

PREREQUISITES

ENSURING KARMA GENERATED JUNIT REPORT SHOWS UP IN JENKINS…

Instead of manually running karma start command in Jenkins, we will rely on maven-karma-plugin to do this for us. The key here is to specify the correct <phase> so that Jenkins picks up and presents the generated report.

pom.xml

<build>
    <plugins>
        <plugin>
            <groupId>com.kelveden</groupId>
            <artifactId>maven-karma-plugin</artifactId>
            <version>1.6</version>
            <executions>
                <execution>
                    <phase>process-test-classes</phase>
                    <goals>
                        <goal>start</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <karmaExecutable>${basedir}/node_modules/karma/bin/karma</karmaExecutable>
                <configFile>src/test/resources/karma.jenkins.conf.js</configFile>
            </configuration>
        </plugin>
    </plugins>
</build>

CONFIGURING JENKINS

Since Karma test runner requires NodeJS, we will install NodeJS Plugin in Jenkins. This allows us to automatically install NodeJS from Jenkins.

Once installed, go to Manage Jenkins -> Configure System and go to NodeJS section:-

Although this section allows us to specify npm packages to install, I’m having trouble installing certain packages, such as karma-phantomjs-launcher. The phantomJS package invokes node install.js during the installation, however, the node command isn’t available in PATH environment variable at this point. Thus, the installation will always fail. So, the npm packages will be configured at the job level in the next step. Further, it is not a good idea to install Karma-related plugins globally here because every Jenkins job may use slightly different versions of the same plugin (think Spring or Hibernate versions in every job’s pom.xml).

Next, create a Maven project job and configure it.

Configuring Build Environment, Pre Steps and Build

We exposed NodeJS to PATH environment variable so that we can install phantomJS package.

Next, we created a pre-build step to execute npm install, which reads package.json from the job and installs the needed dependencies within the job.

Finally, we will want Maven to invoke test goal so that it runs both Java tests and Karma test runner.

Configuring Coverage Report

We provided the Karma generated coverage report XML file.

OUTCOME

When we run Build Now in Jenkins, both unit test and coverage reports will display both Java and JavaScript execution results.

Since we ran npm install as a pre-build step, the job will now have node_modules directory.

IntelliJ : Karma Integration

Overview

Although we can run karma start karma.conf.js on the command line to start Karma test runner, JetBrains provides a great Karma-IntelliJ integration to run and display the JavaScript test results within IntelliJ.

Prerequisites

Install Karma Plugin in IntelliJ

The first step is to install the Karma plugin created by JetBrains.

Create Karma “Run” Configuration

From the drop down list on top right of IntelliJ, select Edit Configurations….

In Run/Debug Configuration dialog, select + and then select Karma.

Enter a name, in this example, we call it Karma.

Use the drop down list to select a configuration file called karma.conf.js.

The Node interpreter and Karma package should already be defined, otherwise, use the drop down lists to select the right values.

On top right of IntelliJ, select the green Play button to run Karma test runner.

IntelliJ should now run Karma and display the familiar green/red bar based on the JavaScript test results.

There are few things we can configure here:-

  • Button 1 – When selected, IntelliJ will automatically rerun all the tests whenever we change the production or test JavaScript files.
  • Button 2 – When unselected, all passed tests are displayed.
  • Button 3 – When selected, all tests are expanded.

Karma: Getting Started

Overview

This tutorial walks through the steps need to configure Karma to work with a Maven web project. It will also be used as a base for my future Karma related posts.

Install Node.js

First, download Node.js from http://nodejs.org/download/ and install it. Once installed, we should be able to invoke npm command from the command line, for example:-

npm -version

Create package.json

Let’s assume we have the following Maven project:-

testKarma
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── webapp
│   └── test
│       └── java
└── testKarma.iml

Please ignore testKarma.iml, which is an IntelliJ specific file.

Create a file called package.json under project root directory…

testKarma
├── package.json
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── webapp
│   └── test
│       └── java
└── testKarma.iml

… with the following content…

{
  "name": "testKarma",
  "private": true
}

In this case, we created a private repository by setting private to true.

Without this flag set, when you run npm install ..., we will get these annoying warnings:-

npm WARN package.json testKarma@ No description
npm WARN package.json testKarma@ No repository field.
npm WARN package.json testKarma@ No README data

Install Karma and Plugins

From the project root directory, run the following command:-

npm install karma karma-jasmine@0.2.2 karma-chrome-launcher karma-phantomjs-launcher karma-junit-reporter karma-coverage --save-dev

At the time of this post, by default, karma-jasmine will install version 0.1.5. So, we will manually specify the latest version that allows us to use Jasmine 2.x.

For local testing, we will run our tests against both Chrome browser and PhantomJS, which is a headless browser. So, make sure Chrome browser is already installed.

The project structure now contains the installed plugins for JavaScript testing:-

testKarma
├── node_modules
│   ├── karma
│   ├── karma-chrome-launcher
│   ├── karma-coverage
│   ├── karma-jasmine
│   ├── karma-junit-reporter
│   └── karma-phantomjs-launcher
├── package.json
├── pom.xml
├── src
│   ├── main
│   └── test
└── testKarma.iml

The package.json contains a history of the plugins we installed within this project:-

{
  "name": "testKarma",
  "private": true,
  "devDependencies": {
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.5",
    "karma-coverage": "^0.2.6",
    "karma-jasmine": "^0.2.2",
    "karma-junit-reporter": "^0.2.2",
    "karma-phantomjs-launcher": "^0.1.4"
  }
}

A couple of important notes:-

  • Basically, since we are not running npm install with -g option, the plugins will not be installed globally. Rather, they are installed within the project root directory.
  • Using --save-dev option, if package.json exists, this file will be automatically updated to keep track of the installed plugins and versions.
  • If we decided to update the plugin versions, we just need to modify this file and run npm install to update them.
  • We do not want to commit node_modules directory into any VCS because there are at least 5000 files in this directory. So, remember to configure a VCS exclusion on this directory (see IntelliJ: Handling SVN Global Ignore List).
  • When other peers check out this project from VCS, they will run npm install, which will automatically install all the dependencies listed in package.json.

Create Karma Configuration File

Instead of running karma init karma.conf.js to step through the process to create a configuration file, we will manually create two Karma configuration files under src/test/resources directory.

testKarma
├── node_modules
├── package.json
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── webapp
│   └── test
│       ├── java
│       └── resources
│           ├── karma.conf.js
│           └── karma.jenkins.conf.js

karma.conf.js

This configuration is used for local testing.

module.exports = function ( config ) {
    config.set( {
        basePath         : '../../../',
        frameworks       : ['jasmine'],
        files            : [
            'src/main/webapp/resources/js/**/*.js',
            'src/test/js/**/*.js'
        ],
        exclude          : [],
        preprocessors    : {
            'src/main/webapp/resources/js/**/*.js' : ['coverage']
        },
        reporters        : ['progress', 'coverage'],
        port             : 9876,
        colors           : true,
        logLevel         : config.LOG_INFO,
        autoWatch        : true,
        browsers         : ['Chrome', 'PhantomJS'],
        singleRun        : false,
        plugins          : [
            'karma-jasmine',
            'karma-chrome-launcher',
            'karma-phantomjs-launcher',
            'karma-junit-reporter',
            'karma-coverage'
        ],
        coverageReporter : {
            type : 'html',
            dir  : 'target/coverage/'
        }
    } );
};

karma.jenkins.conf.js

This configuration is used for automated testing on Jenkins.

module.exports = function ( config ) {
    config.set( {
        basePath         : '../../../',
        frameworks       : ['jasmine'],
        files            : [
            'src/main/webapp/resources/js/**/*.js',
            'src/test/js/**/*.js'
        ],
        exclude          : [],
        preprocessors    : {
            'src/main/webapp/resources/js/**/*.js' : ['coverage']
        },
        // added `junit`
        reporters        : ['progress', 'junit', 'coverage'],
        port             : 9876,
        colors           : true,
        logLevel         : config.LOG_INFO,
        // don't watch for file change
        autoWatch        : false,
        // only runs on headless browser
        browsers         : ['PhantomJS'],
        // just run one time
        singleRun        : true,
        // remove `karma-chrome-launcher` because we will be running on headless
        // browser on Jenkins
        plugins          : [
            'karma-jasmine',
            'karma-phantomjs-launcher',
            'karma-junit-reporter',
            'karma-coverage'
        ],
        // changes type to `cobertura`
        coverageReporter : {
            type : 'cobertura',
            dir  : 'target/coverage-reports/'
        },
        // saves report at `target/surefire-reports/TEST-*.xml` because Jenkins
        // looks for this location and file prefix by default.
        junitReporter    : {
            outputFile : 'target/surefire-reports/TEST-karma-results.xml'
        }
    } );
};

Write JS Production Code and Test Code

Now, it’s time to write some tests and run them.

testKarma
├── node_modules
├── package.json
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── webapp
│   │       └── resources
│   │           └── js
│   │               └── hello.js
│   └── test
│       ├── java
│       ├── js
│       │   └── hello-spec.js
│       └── resources
│           ├── karma.conf.js
│           └── karma.jenkins.conf.js
└── testKarma.iml

hello.js

We will keep our production code to the minimum in this example:-

var hello = {
    speak : function () {
        return 'Hello!';
    }
};

hello-spec.js

A very simple test case:-

describe( 'hello module', function () {
	'use strict';
    it( 'speak()', function () {
        expect( hello.speak() ).toBe( 'Hello!' );
    } );
} );

Run Karma using karma.conf.js

From the project root directory, run Karma:-

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

The console should now look like this:-

INFO [karma]: Karma v0.12.24 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket YNMziWTsyeaf6DZzw06D with id 3320523
INFO [Chrome 38.0.2125 (Mac OS X 10.9.5)]: Connected on socket rYQeni1xm1bbqfa3w06E with id 50259203
PhantomJS 1.9.8 (Mac OS X): Executed 1 of 1 SUCCESS (0.003 secs / 0.001 secs)
Chrome 38.0.2125 (Mac OS X 10.9.5): Executed 1 of 1 SUCCESS (0.008 secs / 0.001 secs)
TOTAL: 2 SUCCESS

The target/coverage directory should contain Chrome and PhantomJS subdirectories.

testKarma
├── node_modules
├── package.json
├── pom.xml
├── src
├── target
│   └── coverage
│       ├── Chrome 38.0.2125 (Mac OS X 10.9.5)
│       │   ├── index.html
│       │   ├── js
│       │   │   ├── hello.js.html
│       │   │   └── index.html
│       │   ├── prettify.css
│       │   └── prettify.js
│       └── PhantomJS 1.9.8 (Mac OS X)
│           ├── index.html
│           ├── js
│           │   ├── hello.js.html
│           │   └── index.html
│           ├── prettify.css
│           └── prettify.js
└── testKarma.iml

When opening one of the index.html files, we should see the coverage report.

Run Karma using karma.jenkins.conf.js

Although we will only use karma.jenkins.conf.js for automated testing on Jenkins, we will run this configuration file to see the generated output differences.

Clean the target directory:-

mvn clean

Run Karma with karma.jenkins.conf.js instead:-

node_modules/karma/bin/karma start src/test/resources/karma.jenkins.conf.js

Since this configuration only runs on the headless browser, we will not see Chrome results here:-

INFO [karma]: Karma v0.12.24 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket SM_5sy2wzHdL4ru9yg0X with id 42922522
PhantomJS 1.9.8 (Mac OS X): Executed 1 of 1 SUCCESS (0.002 secs / 0.001 secs)

The target directory should contain cobertura-coverage.xml for coverage result and TEST-karma-results.xml for test result.

testKarma
├── node_modules
├── package.json
├── pom.xml
├── src
├── target
│   ├── coverage-reports
│   │   └── PhantomJS 1.9.8 (Mac OS X)
│   │       └── cobertura-coverage.xml
│   └── surefire-reports
│       └── TEST-karma-results.xml
└── testKarma.iml