Using Istanbul With ES2015+

In this tutorial I'll walk you through setting up test coverage for your fancy pants ES2015+ JavaScript projects. We'll cover two topics: instrumenting native JavaScript code, and writing backwards compatible ES2015+ code using babel.

For this tutorial, feel free to follow along in the repository bcoe/es2015-coverage

#ES2015+ With @babel/preset-env

ES2015+ is composed of a daunting array of JavaScript language features. Babel comes to the rescue, providing collections of plugins that track the standardization process.

For this tutorial we will be using the env preset, a collection of Babel plugins that encompass many of the exciting features currently in the pipeline for JavaScript.

#Prerequisites

By relying on the preset, there are only a few dependencies that we need to add test coverage to our ES2015 project:

npm i @babel/cli @babel/register babel-plugin-istanbul @babel/preset-env cross-env mocha chai nyc --save-dev
  • @babel/cli: is the command-line interface for babel; we use it during the build step.
  • @babel/register: automatically compiles ES2015+ JavaScript as it's required in your tests.
  • babel-plugin-istanbul: this plugin adds coverage instrumentation to your ES2015+ code as it's compiled.
  • nyc: outputs the coverage information to disk, and handles running reports.
  • cross-env: used to set NODE_ENV=test in a cross-platform compatible way.
  • mocha/chai: the test framework that I happen to be using for this tutorial.

#Configuration

.babelrc

We place a .babelrc in the root of our project which is used by @babel/cli to apply the compilation process:

{
  "presets": [
    "@babel/preset-env"
  ],
  "env": {
    "test": {
      "plugins": [
        "istanbul"
      ]
    }
  }
}
  • presets: indicates that we should load the @babel/preset-env set of plugins, which is kept up to date for us.
  • env.test.plugins.istanbul: indicates that we should run the babel-plugin-istanbul plugin only when NODE_ENV=test.

package.json: configuring nyc

{
  "nyc": {
    "require": [
      "@babel/register"
    ],
    "reporter": [
      "lcov",
      "text"
    ],
    "sourceMap": false,
    "instrument": false
  }
}
  • nyc.require.@babel/register: indicates that we should automatically run require('@babel/register') as nyc loads our tests. This allows us to write ES2015+ code in our tests without running a build step (code is automatically compiled by babel as it is loaded).
  • nyc.sourceMap=false/nyc.instrument=false: indicates that we should not use nyc for instrumenting tests with coverage or handling source-maps; this logic is instead handled by babel and babel-plugin-istanbul.
  • nyc.reporter: A list of reporters that you can use, all of which come from istanbul. In this case we telling nyc to ask for an lcov which gives you a set of html reports for each file instrumented as well as an lcov.info file which can be sent to coverage services like coveralls.io or codecov.io. The text reporter gives a table summary of each file's coverage in the terminal.

package.json: script stanza

{
  "scripts": {
    "build": "babel index.js -d src",
    "test": "cross-env NODE_ENV=test nyc mocha test.js",
    "prepublish": "npm run build"
  }
}
  • scripts.test:

    • cross-env NODE_ENV=test: sets the NODE_ENV environment variable resulting in the babel-plugin-istanbul plugin being loaded.
    • nyc: indicates that nyc should be used to run mocha.
    • mocha test.js: it doesn't get much simpler than this; use mocha to run test.js.
  • scripts.build: this script uses babel-cli to compile your ES2015+ code.
  • scripts.prepublish: we automatically run the build step before publishing our package to npm.

#Writing Tests

Because nyc automatically loads @babel/register there is no build step necessary for your tests. Just write your tests using ES2015+ syntax and require() the pre-compiled ES2015+ JavaScript files:

import CoverageBabel from './index'

require('chai').should()

describe('CoverageBabel', function () {
  it('returns hello world message', function () {
    const cls = new CoverageBabel('Ben')
    cls.helloMessage().should.equal('hello Ben')
  })
})

#Shipping Your Code

When it's time to publish your code to npm, simply use @babel/cli to compile your ES2015+ JavaScript into ES5 compatible code:

babel index.js -d src

This command will read in your ES2015+ ./index.js file and output the ES5 ./src/index.js file. Only ./src/index.js should be published to npm, which can be achieved by adding a files stanza to your package.json:

{
  "files": [
    "src/index.js"
  ]
}

#Instrumenting Native ES2015+ Code

If you're using newer versions's of Node.js various ES2015+ features are already supported. node.green provides a useful chart for viewing compatibility information.

nyc uses the same parser underneath the hood as babel and understands native ES2015+ constructs; with zero configuration you can start instrumenting your ES2015+ code!