Using Istanbul for code coverage
I’ve been using Istanbul, a JS code coverage library. Istanbul outputs a report that shows which lines are hit during unit tests:
Istanbul can be installed with:1
npm install -g istanbul
How Istanbul works
Istanbul wraps all your code with functions that track how often each function, line, and branch in the code got hit while running unit tests. It stores this data in a coverage.json
file which can then be used to generate html, lcov, or other kinds of reports.
The process of wrapping code with tracking functions is called “instrumentation”. Istanbul relies on the esprima package to parse JS source code. Then escodegen is used to add in the coverage code and to output a new instrumented source file.
Given a source file person.js
that looks like:
1 | function Person(name, dob) { |
Istanbul can create the instrumented code via: istanbul instrument --no-compact -o tmp/instrument/person.js person.js
. This creates a instrumented person.js
file in tmp/instrument/person.js
.
The tmp/instrument/person.js
file looks like this. I added some comments to detail what is happening:
1 | // a temp __cov_X9N6pGXtRbutT8pzNmQHeA obj is created |
So as the instrumented code is executed, a tmp object stores all the information about what lines have been called for a particular file. Note that the istanbul instrument
command is only useful for browser based tests. I included the instrumented code here just to illustrate how Istanbul keeps track of coverage. For tests that can run on node, the regular istanbul cover
command automatically instruments the code.
Istanbul for Node tests
If we had a jasmine test file person.spec.js
like this:
1 | var Person = require('./person'); |
This can be run with jasmine person.spec.js
:
1 | Started |
Now to get the coverage of this file person.spec.js
run istanbul cover jasmine person.spec.js
to get the following output:
1 | Started |
The report is generated at coverage/lcov-report.index.html
:
Note that the statement % coverage is very high (88%). This is misleading because the tests only cover half of the branches (50%). Be sure to check coverage % across statements, branches, and functions.
Istanbul for browser tests
The previous example showed how to run Istanbul on node code. Running Istanbul on the browser requires a little more setup. For this example I’ll use the following:
- PhantomJS. Phantom is a headless browser that will allow us to test frontend UI code. jsdom is another option, however jsdom does not handle things like hidden/visible elements properly.
- Grunt. I’m mainly using Grunt as the static server via grunt-contrib-connect that can serve up tests for Phantom, and also write down the coverage data.
- Jasmine for running the unit tests.
- grunt-contrib-jasmine A Grunt plugin that allows us to run Jasmine tests via phantom.
There are a few steps to get Istanbul tests on the browser:
- Manually instrument the source code via
istanbul instrument
- Make the specs use the instrumented source code instead of the actual code
- Get the coverage data from
window.__coverage__
after all tests have been run - Send the data from
window.__coverage__
to a server so that the coverage data can be saved - Run
istanbul report
to generate a report out of the coverage data
The full repo of a working demo is here: https://github.com/jiahuang/istanbul-phantom-jasmine
NYC
NYC is the newer Istanbul CLI that works with react & ES6. The problem with getting code coverage for ES6 is that only the babel-transformed version of the code actually runs. So a code coverage tool has to un-babelify the code that was run to get the original source code via a source map. Istanbul cannot currently do this, but NYC can.
One of the drawbacks of Istanbul is that it only checks files that have been required. There’s an open issue to fix this, but for now a workable solution seems to be just require all files. Being able to see code coverage makes it easier to discover fragile pieces of untested code, and write tests to cover edge cases.