Gulp, Mocha and Istanbul

Yes… you read that right, I am still using gulp. Recently, with all the hype and the fact that most of the people I know are switching to npm scripts, gulp seems to be something in the distant past. But, here I am, still using gulp because with gulp or similar task manager libraries, you can share the tasks between your projects. Or you can even have a set of tasks that you usually use in your side projects in one git repository and re-use them every time you have some brilliant ideas and want to roll a new side project (and quickly abandon it like I often do).

In my thesis, I am writting a framework consisting of around 10 sub-projects, copy and paste npm scripts is a job that I definitely don’t want to do. Imaging that you have to change something in one of the npm scripts in one of the projects and you have to go though all 10 projects to update the change. Of course, there are ways to automate that, good old bash scripts or maybe even a JS file to do it. But the point is that I prefer having common stuff in one place and when I modify something, the rest will instantly get the new stuff.

Mocha

Mocha is a fun, simple, flexible JavaScript test framework. It has been around for a very long time. My goal is to have a simple gulp test task using mocha, so that I can run gulp test to test my projects. Since the projects’ structure are identical, having a common test task is straightforward.

gulp.task('test', function() {
  const cwd = process.cwd();

  loadSpecs();

  // set all the tools needed for testing globally
  global.expect = require('chai').expect;
  global.sinon = require('sinon');

  mocha.run(function(failures) {
    process.on('exit', function () {
      process.exit(failures);
    });
  });
});

All the black magic happens in loadSpecs which simply goes through all the folders inside src folder recursively to load spec files (having .spec in the file name)

function getAllFilesFromPath(path) {
  const stat = fs.statSync(path);
  if (stat.isDirectory()) {
    return _.chain(fs.readdirSync(path)).map(function(file) {
      return getAllFilesFromPath(path + '/' + file);
    }).flatten().value();
  } else if (stat.isFile()) {
    return [path];
  }
  return null;
}

function loadSpecs() {
  const cwd = process.cwd();
  // I put all the modules in src folder in each project
  getAllFilesFromPath(`${cwd}/src`).forEach(function(path) {
    if (path.indexOf('.spec') != -1) {
      mocha.addFile(path);
    }
  });
}

Since this is only for testing, I don’t need to optimize anything, just use statSync and readdirSync. And that is the simplest from of mocha setup for gulp. More info about how to use mocha programmatically

Istanbul

Istanbul is a test coverage library to let people know how well their tests cover the source code. Using istanbul from command line is the easiest thing I have seen so far, just run it with mocha like this nyc mocha and you are good to go. However, using it programmatically requires a bit more work. In fact, I needed to dig around for a solid 57 minutes to figure out how to make it work with mocha. The secret sauce is this hook which can be access through istanbul.hook. Let me show the code first and then explain it

const istanbul = require('istanbul')

function hookRequire() {
  const instrumenter = new istanbul.Instrumenter();
  const cwd = process.cwd();
  const fileMap = {};
  istanbul.hook.unhookRequire();
  istanbul.hook.hookRequire(function (path) {
    const parts = path.split(cwd).filter(Boolean);

    return !fileMap[path]
      // ignore node_module
      && path.indexOf('node_module') === -1
      // ignore spec files
      && path.indexOf('.spec') === -1
      // ignore anything outside the current working directory
      && parts.length === 1
      && parts[0].indexOf('/src') === 0;
  }, function (code, path) {
    fileMap[path] = instrumenter.instrumentSync(code, path);
    return fileMap[path];
  });
}

Note that hookRequire needs to be called before you actually require anything. In this example, it should be called before loadSpecs. The idea is to hook into require and alter the required module to let istanbul instrument the code. However you don’t want to instrument everything, for example, stuff from node_module or your spec files.

istanbul.hook.hookRequire accepts 2 params which are 2 functions and another optional options object. The first one is the matcher which checks whether a module needs to be transformed (instrumented) or not. The second one is the transformer which is where I use istanbul.instrumenter to instrument the code. Then, when my tests run through the already instrumented code, istanbul records the coverage (default to global.__coverage__). The coverage data later can be used to generate a report.

Another thing worth noticing is that the whole thing is synchronous because of the way nodejs requires modules. It always happens synchronously and there is nothing we can do about it. It might be slower (compared to the asynchronous counterpart) but it does the job.

The final piece is to generate a coverage report. In my case I only need 2 reports, text and html. text report is the one you will see when your tests end. html report contains a lot more information such as the coverage percentage of each file.

function summarizeCoverage() {
  const cwd = process.cwd();
  const coverage = global.__coverage__;
  if (!coverage) {
    console.warn('Unable to find coverage data');
    return;
  }

  const collector = new istanbul.Collector();
  collector.add(coverage);

  const reporter = new istanbul.Reporter(null, `${cwd}/coverage`);
  reporter.addAll([ 'text', 'html' ]);
  reporter.write(collector, true, () => {});
};

With all that said and done, I have a re-usable test task that I can use in my side projects. I might need to modify it a bit to adapt to my future project structure, but for now it’s good enough for my needs.

What wrong with npm scripts?

I have nothing against npm scripts, in fact I have been using them regularly for the past 2 years. It’s just that I need something that can be shared between my projects.