Code Coverage with es6, Babel, Karma, Mocha, and Webpack

I was trying to get JavaScript unit testing set up at work and was really struggling. While I could get unit tests and established a testing framework I wasn’t able to determine my code coverage. For those of you that haven’t tested before, code coverage is a really important aspect of unit tests because it will tell you where you have gaps in your tests. If you’re not testing a specific branch or function you have a greater chance of pushing out a bug because the code isn’t tested.  So code coverage is pretty important.

Take this piece of my devDependencies within my package.json.

package.json:

"devDependencies": { 
    "babel-core": "6.23.1", 
    "babel-loader": "6.3.2", 
    "babel-preset-es2015": "6.22.0", 
    "babel-preset-stage-1": "6.22.0", 
    "chai": "3.5.0", 
    "gulp-mocha": "4.0.1", 
    "karma": "1.5.0", 
    "karma-cli": "1.0.1", 
    "karma-coverage": "1.1.1", 
    "karma-mocha": "1.3.0", 
    "karma-phantomjs-launcher": "1.0.2", 
    "karma-sinon-chai": "1.2.4", 
    "karma-webpack": "2.0.2", 
    "mocha": "3.2.0", 
    "sinon": "1.17.7", 
    "sinon-chai": "2.8.0", 
    "webpack": "2.2.1" 
} 

This is a pretty standard build out when you’re using es6 (Babel) and are transpiling down from 6 to 5 as part of your bundling/minifying/uglifying process (Webpack). I chose the Karma/Mocha/Sinon/Chai technology stack simply because it’s widely adopted and lightweight. Some people prefer Jasmine more, which is fine, Jasmine comes with a lot of this functionality built-in, but I like to have options.

A lot of sites tell you to set up your karma.conf.js file something like this:

karma.conf,js


const src = './scripts/**/*.js',
      tests = './tests/**/*.spec.js';

const karmaConfig = {
    frameworks: ['mocha', 'sinon-chai'],
    files: [
        src,
        tests
    ],
    preprocessors: {
        src: ['webpack', 'coverage'],
        tests: ['webpack']
    },
    webpack: webpackConfig('test'),
    reporters: ['coverage'],
    browsers: ['PhantomJS'],
    client: {
        captureConsole: false
    },
    specReporter: {
        showSpecTiming: true
    },
    reportSlowerThan: 25,
    coverageReporter: {
        dir: 'coverage',
        reporters: [
            { type: 'text' },
            { type: 'text-summary' },
            { type: 'html' }
        ]
    }
};

karmaConfig.preprocessors[src] = ['webpack', 'coverage'];
karmaConfig.preprocessors[tests] = ['webpack'];

module.exports = function (config) {
    config.set(karmaConfig);
}

If you have something like this you’ll get bundled output and your tests will run, but your test coverage will be extremely low. If you add the coverage reporter to the tests you’ll likely get an error saying you can’t require the module because the module is already required. You’ve created a cyclical reference.

What we want really is to run the tests, and from the tests determine what source code was executed by the tests. But we also need to run the tests and code coverage on the non-transpiled code. In order to do this we need to include another handy babel package:

npm install babel-plugin-istanbul --save-dev

Istanbul is pretty much the gold standard when it comes to coverage reporting in the front-end world and it does this extremely well. I also have a JSON file of config settings, that currently has this node for Babel:

config.json


"babel": {
     "presets": [
          ["es2015", {
               "modules": false
          }],
          "stage-1"
     ]
}

So I need to add another configuration to this file:


"babel": {
     "presets": [
          ["es2015", {
               "modules": false
          }],
          "stage-1"
     ],
     "plugins": [
          ["istanbul", {
               "exclude": [
                    "**/*.spec.js"
                ]
          }]
     ]
}

You’ll notice a line about exclusions. This plugin by default will exclude certain naming conventions from your code coverage. If you don’t use one of the standards (which sometimes I don’t) it will include the test code files as part of your code coverage report – not good!  So I just add in the path to my test files to prevent this.

We also need to remove the coverage reporter from the files but leave the coverage reporter settings. This seems wrong but I promise this works. We also don’t want to include source files in our karma.conf.js file because right now we only want to get coverage for files that we have created a test file for. Why? Well what if we had a vendor script? If we included that file we’d be penalized for not writing unit tests on the file or maybe we’d write unit tests on the file anyway, which could be troublesome if it’s not written for testability. Ideally third-party and vendor scripts have been tested during the development phase.


const tests = './tests/**/*.spec.js';

const karmaConfig = {
    frameworks: ['mocha', 'sinon-chai'],
    files: [
        tests
    ],
    preprocessors: {
        tests: ['webpack']
    },
    webpack: webpackConfig('test'),
    reporters: ['coverage'],
    browsers: ['PhantomJS'],
    client: {
        captureConsole: false
    },
    specReporter: {
        showSpecTiming: true
    },
    reportSlowerThan: 25,
    coverageReporter: {
        dir: 'coverage',
        reporters: [
            { type: 'text' },
            { type: 'text-summary' },
            { type: 'html' }
        ]
    }
};

karmaConfig.preprocessors[tests] = ['webpack'];

module.exports = function (config) {
    config.set(karmaConfig);
}

Now when we run our tests we will get coverage only on the files that we want to have coverage for. Look out for posts on how to write unit tests and how to write your code to be testable in the future. There will also be a post on how to test third-party scripts, because sometimes it’s a critical component and you need the certainty that upgrades won’t break functionality on the site.

Pin It

You must be logged in to post a comment.