Dan Stutzman

Which lines of your JavaScript project have test coverage?

Published 2012-11-11

If you write automated tests (as I do) but don't strictly follow the TDD principle of writing the test first (I usually don't), you'll find yourself with some code that is covered by automated tests, and some code that is not. So I suggest you use a tool that shows you exactly which lines of source code are covered.

Why measure this when you already know a high-priority code area that obviously lacks coverage, and you're not sure you'll even get around to it? Because the reason we procrastinate is that we doubt we can make a difference. With a tool you can see the pool of uncovered lines get smaller and smaller.

I'll show you my current setup for measuring test coverage in a JavaScript project. There's probably a better way to do it, and there will definitely be newer better ways in a couple more years as the JavaScript ecosystem further matures. For example, I almost got blanket.js working but had trouble combining it with require.js and didn't want to rewrite my Jasmine tests as QUnit anyway.

1. Concatenate requirejs-enabled code

          node tools/r.js -o tools/rjs-build-config.js optimize=none
          

The biggest problem I had was tools expecting the JavaScript to be loaded with the page, not afterward via script injection as require.js does. So first I run r.js to concatenate my requirejs-wrapped CoffeeScript into a single JavaScript file that I'll load with a <script> tag. I'm reusing my existing build config except with optimize=none so that the source lines are still readable and separated. This outputs to the www-built directory.

2. Instrument the source code

          java -jar ../JSCover/target/dist/JSCover-all.jar -fs www-built www-coverage
          

Then I call JSCover to copy the www-built directory to the www-coverage directory and insert lines of instrumentation code to track whether or not each line of my original code was reached in execution. Below is an example:

          _$jscoverage['js/main.js'][502]++;
          low = 0;
          _$jscoverage['js/main.js'][503]++;
          high = haystack.length - 1;
          

3. Run the tests and see the report

          open "http://localhost:8888/www-coverage/TestRunner-prod.html?$PARAMS"
          

Finally I run the Jasmine tests. I wrote a primitive report that dumps out the source code in a <pre> and prints a star beside any lines that aren't covered:

          function run_jscover_report() {
            out = '';
            out += "<pre>\n";
            for (filename in _$jscoverage) {
              if (filename == 'branchData') continue;
              for (line_num in _$jscoverage[filename]['source']) {
                var count = _$jscoverage[filename][parseInt(line_num) + 1];
                out += ((count == 0) ? "* " : "  ");
                out += (line_num);
                out += (_$jscoverage[filename]['source'][line_num]);
                out += ("\n");
              }
            }
            out += ("</pre>\n");
            return out;
          }
          
          if (typeof(top._$jscoverage) !== 'undefined') {
            jasmine.getEnv().addReporter({
              reportRunnerResults: function() {
                var report = run_jscover_report();
                jQuery('body').append(report);
              }
            });
          }
          

Here's an excerpt of that report: (yes, the line numbers are one off for some reason)

            501      low = 0;
            502      high = haystack.length - 1;