Natively Unit-Testing ES6 Modules in Browser Including Coverage

by Thomas Urban

Recent versions of most common browsers support ES6 modules natively. Before that transpilers like webpack or babel have been used to convert code to something browsers were capable of processing back then. This includes browser-side unit testing.

In some cases using transpilers might cause side effects on tested code and test implementations. That's why it is time to upgrade your testing to the present. This is a brief tutorial on how to achieve that.

Some Context First

Consider a project folder containing implementation files in sub-folder src/ and unit testing code in test/ with either test implementation file having extension .spec.js. An implementation might look like this file src/core/main.js:

export class MainCore {
    static someFeature( input ) {
        return 2 * input;
    }
}

There should be a test implementation in a file like test/core/main.spec.js:

import { MainCore } from "../../src/core/main.js";

describe( "Class MainCore", () => {
    it( "doubles provided value on using static method someFeature()", () => {
        MainCore.someFeature( 5 ).should.be.equal( 10 );
    } );
} );

This test implementation is meant to rely on mocha for test-running and on shouldjs for assertions. There are different tools for either task but we have decided to stick with a particular set that we deemed to be suitable for all our software.

Setting Up Karma

Karma is a tool for running unit tests in a browser. Mocha is a test runner as well, but it's meant to run on command line using Node.js. It is great for server-side code implemented in Javascript. Karma is another command line tool moving mocha into a browser enabling it to test browser-side features, as well.

Install Karma And Its Plugins

Install karma in your project with

npm i -D karma

In addition you need some plugins:

npm i -D karma-mocha karma-should 

These two are used to expose mocha and should in browser (thus you don't need to import them in your test implementations as demonstrated before).

npm i -D karma-chrome-launcher karma-firefox-launcher karma-edge-launcher

These plugins are required for controlling browsers to run your unit tests. There are plugins for all major browsers and you should install launcher for either one you want to test your code with. Consider Edge for local testing, only, as it might be missing in a CI context.

npm i -D karma-coverage-istanbul-instrumenter karma-coverage-istanbul-reporter

Finally, these two are highly recommended for assessing your tests' quality and for identifying that part of your code which hasn't been tested well, yet.

Configure Karma

Karma checks a configuration file named karma.conf.js in root folder of your project. You can have any other file as well and pick it on invoking karma as described below.

Your configuration file should look like this:

const Path = require( "path" );

module.exports = function( config ) {
    config.set( {
        frameworks: [
            "mocha",
            "should",
        ],

        files: [
            // tests
            { pattern: "test/unit/**/*.spec.js", type: "module" },
            // files tests rely on
            { pattern: "src/**/*.js", type: "module", included: false },
        ],

        reporters: [ "spec", "coverage-istanbul" ],

        browsers: ["ChromeHeadless"],

        singleRun: true,

        preprocessors: {
            "**/!(*.spec).js": ["karma-coverage-istanbul-instrumenter"]
        },

        coverageIstanbulInstrumenter: {
            esModules: true
        },

        coverageIstanbulReporter: {
            reports: [ "html", "text" ],
            dir: Path.join( __dirname, "coverage" ),
        },
    } );
};

Let's see what this is about:

  • First block of configuration named frameworks is listing frameworks used for testing.
  • Next block is selecting files to expose for use in browser. Karma is running a web service providing all matching files.
    • pattern is a glob pattern selecting files related to this configuration file.
    • type is set to module to enable browser's support for ES6 modules on those files.
    • included is set false on files that aren't meant to contain test implementation but some actually tested code.
  • reporters is listing some reporters used to display results of running unit tests:
    • spec is listing run tests and their result.
    • coverage-istanbukt is enabling integration of karma-coverage-istanbul-reporter. It is configured separately. Just see below.
  • browsers is an array listing browsers to run tests in. For every named browser the related launcher must be installed. Some launchers cover multiple browsers you can use here. E.g. Chrome and ChromeHeadless are both supported by karma-chrome-launcher.
  • singleRun is a boolean controlling whether running your tests once, only, or watching your selected files for changes and re-run tests each time either file is changing on disk. You can control this when invoking karma using option --single-run or --no-single-run.
  • preprocessors usually integrate transpilers. Code coverage usually is integrated with transpilers. For we don't want transpilers we still need to integrate code coverage tools here. Since we don't care for the coverage of test implementation the given rule is meant to apply to every Javascript file that doesn't use extension .spec.js.
  • Final two blocks are given in example for karma-coverage-istanbul-instrumenter. The essential difference here is the selection of enabled coverage reporters: html is used to create a set of HTML files in folder given by option dir showing a summary and either file's source code with missing lines of code highlighted. text is displaying a tabular summary as part of your test runner's output (causing it to appear in console or in CI logs).

Run The Tests

Basically running tests is as simple as invoking

karma run

on command line of your project. You should have a script in your package.json

...
"scripts": {
    "test": "karma run"
}
...

This way you can have different configurations using different invocable scripts:

...
"scripts": {
    "test": "karma run",
    "test:dev": "karma run karma.alt.conf.js --browsers=Firefox,Chrome,Edge"
}
...

This example adds another script using different configuration file and adjusting set of browsers to run.

Setting Up CI

We use GitLab for CI and this is a working configuration suitable for running Chrome-based unit tests. Put this code into a file .gitlab-ci.yml in your project's root folder:

image: "cepharum/e2e-chrome-ci"

test:
  stage: test
  variables:
    NODE_ENV: development
  script:
    - npm install
    - npm run test
  artifacts:
    paths:
      - coverage
    name: coverage

This configuration is adding a CI task running your package.json-based script named test. In addition it is picking up HTML-based coverage report from sub-folder coverage/ and expose it as an artifact on pipeline view of GitLab.

Go back