Get it? Mocha / reporter? No? Nevermind.

Warning: This article is basically tech arts-and-crafts.

I want to display a screen in the office showing the latest unit test results, like a notice board, so the whole team can see the test status —and we wont miss failing tests.

We use Mocha to run unit tests in Node (nodejs). Jenkins watches our git repo and runs tests when we push new code.

The final product (github, and npm package) is viewed in a browser, refreshed automatically and looks something like this:

Introduction

With Mocha, we can run $ mocha ./src/**/*.test.js to produce a console report that looks something like this:

Borrowed from the mochajs website. Our real test suite is larger. I promise.

But did you know you can output test results in any format you like using a reporter? Mocha even provides a bunch of example / fun reporters build in.

We can use:

mocha ./src/**/*.test.js --reporter <reporterName>

the --reporter flag allows us to use a custom reporter.

Single File or Package?

Writing your own custom reporter can be as simple as created a single Javascript file in an existing project, and referencing it using the -reporter flag.

To demonstrate, Mocha provides example custom reporter code, which we can save in the root of our project and reference like: mocha ./src/**/*.test.js -reporter reporter.js

Alternatively, you may want to wrap your reporter up in a package to use elsewhere. This is a bit beyond the scope of this article, however, I should mention that I have had issues with mocha not recognising local packages.

Getting started

The example code actually works rather well and is very straightforward. We simply export a single function (or reporter) which is passed a “runner” object providing some events we can hook into.

# Example code from Mocha - we just export this one function

module.exports = MyReporter;

function MyReporter(runner) {

...

}

Our custom reporter is going to take the following format:

function MyReporter(runner) {

// 1. Store intermediate values

// to be inserted into the HTML template later



// 2. Update these values when tests run, pass or fail.



// 3. When the runner ends, update the HTML file

}

The final result can be seen in apb-mocha-reporter/src/apbmochareporter.js on github.

Calculating the values we want to display

Based on the example code, I began by writing hooks for the ‘pass’ and ‘fail’ events, summing up the number of tests in each category to get the values we will insert into our HTML. I later discovered that these values are already tallied up for me by runner.stats so I’ll use that instead.

However, I also want to output detailed test results in JSON format, so I still need to write some hooks. I start by declaring some function scoped arrays to hold the test results, then I push into those arrays whenever an event hook fires. Here’s an example for a test ‘pass’ hook:

var json_passes = [];

runner.on('pass', function(test){

json_passes.push(test);

update() // print to the console.

});

Now we have total control of the output — Let’s format it as HTML.

HTML Output — template engine in 12 lines of code.

Programmatically generating or producing HTML is not rocket science. The typical approach is:

Build a HTML template close to what you want. Identify the variable parts of the template you want to change. Write your code to get the replacement parts. Use a templating engine to load the HTML and replace the variables with the replacement parts.

There are loads of templating engines out there. Mustache is one. Handlebars, doT and EJS also do the job. They all basically work the same, but they all represent an extra dependency that we don’t need. We’re not doing anything complex here, and I’d rather keep this package with zero dependencies. So build it ourselves.

Artists impression of a lake surrounded by red cabbage and carrots fields.

Regex Replacement

Regular expressions (regex) are a wonderful, powerful and complex concept that I wont get into in too much detail. Regex are not unique to Javascript, and even within JS, there are multiple ways to utilise them. In our case, we’ll use the string.replace() method. (full documentation here)

str.replace(regexp|substr, newSubstr|function)

We’re going to take a string called template and find every occurrence of words surrounded by double squiggly brackets like so: {{word}} . Then we’re going to replace that word with a value we have generated — in this case, the output of our test.

For example, say we have calculated the percent of tests that ran successfully as: run_percent , and in our template we have written {{run_percent}} . We could write: template.replace(/{{run_precent}}/g, run_percent)

The regex literal is surrounded by forward slashes. The ‘g’ means search globally.

But we can do better. We can find any word surrounded by squigglies with the following regex: /{{.*?}}/g

. means any character except a newline.

* means match the previous thing 0 or more times.

? means make the previous thing ‘non-greedy’ — ie. The shortest possible match.

Finally, we’re not just going to replace every work in squigglies with just one value. If we pass a function as the second parameter to replace() , we can be much smarter…

let newContent = template.replace(/{{.*?}}/g, function(match){

return replacements[match]

})

Here, Match is the word that is found by the regex. replacements is an object with keys for each of our template variables (the squigglies), and values for what we want to replace them with. Here’s an example:

// Example of replacements object

{

"{{lastrun_date}}": new Date(),

"{{run_percent}}": run_percent,

"{{run_numerator}}": sum,

"{{run_denominator}}": total

}

We wrap it all up with the file reading/writing code in a nice little function called updateBillboard and we have our templating engine in 12 lines of Javascript.

Finally, the updateBillboard function is called from the runners end event.

Handling Command Line Arguments

True story: I posted this image to /r/programmerhumor and it was removed as a violation of Rule #3: No low quality, low effort posts.

I want the user to be able to control how the reporter works. The most simple example is to prevent console output.

Mocha provides a basic way to pass command line arguments to the reporter through the —-reporter-options flag. This option isn’t particularly well documented — the main documentation only says:

Usage: mocha [debug] [options] [files]

Options:

...

-O, --reporter-options <k=v,k2=v2,...> reporter-specific options

...

This means we can use either -O or —- reporter-options followed by a comma separated list of options (no spaces!). Each option may have a value, provided after an equals sign. This value is optional.

What isn’t described anywhere (but the example code) is how the reporter-options are passed on the reporter:

function MyReporter(runner, options) {

const SILENT = options.reporterOptions.silent

...

}

A second options parameter is passed to the reporter function as an object containing a reporterOptions object, that contains each option and its value.

Example:

// Test command

mocha ./**.test.js --reporter MyReporter --reporter-options silent,savejson=example

Gives us the following in our custom reporter:

// console.log(options) inside MyReporter()

{

reporterOptions: { silent: true, savejson: 'example' },

globals: [],

files: []

}

If no value is provided (like for silent ) the default is the boolean true .

Using the command line arg to prevent logs

To control when logs happen, we’ll store the value of the silent option and write a simple logging function to check it:

const SILENT = options.reporterOptions.silent

function write(str){

if(!SILENT) process.stdout.write(str);

}

Now we replace all logs with write() and when the silent option is passed, nothing happens!

I’m using process.stdout.write(str); so I can manually handle new lines (

) vs carriage returns ( \r ) to overwrite previous output.

Conclusion

And Voila!

Our test status is displayed somewhere in a typically dull office environment. Fake plant and all.

I searched briefly for existing jenkins plugins / mocha reporters but didn’t find anything 100% right. Also, this was fun.

The package isn’t totally complete yet. The most notable omission is (ironically) unit tests. I’m busy preparing for my PhD viva in a couple weeks, so hopefully I’ll get around to those after.

If you have suggestions for features, please let me know on github!

I’m Aidan Breen, and I run a software consultancy in Dublin, Ireland. If you enjoyed this post, consider following me on twitter, or signup to my personal mailing list for less than monthly updates.