Test mole

Easily test code in private closures using test-mole.

Testing every piece of JavaScript code is hard. It is even harder when you write good JavaScript code - full of private closures. Even Nodejs code using CommonJs modules is hard to test because it only exports some functions, leaving the rest hidden and private.

add.js
1
2
3
4
5
6
7
8
9
10
function isNumber(x) {
return typeof x === 'number';
}
function add(a, b) {
if (isNumber(a) && isNumber(b)) {
return a + b;
}
return 'arguments should be numbers';
};
module.exports = add;

Because function isNumber is private to the module, we can only test it indirectly via testing add. Ensuring full coverage via top level function is a hard problem. A typical solution offered by testing books (for example see "Quality Code" by Stephen Vance, Chapter 9 "Adjusting Visibility") is to relax the privacy of the code to be tested, in this case exposing isNumber to the outside world. CommonJs format only allows exporting single value, leading to awkward external interfaces

add.js
1
2
3
4
5
6
7
8
9
10
11
12
13
function isNumber(x) {
return typeof x === 'number';
}
function add(a, b) {
if (isNumber(a) && isNumber(b)) {
return a + b;
}
return 'arguments should be numbers';
};
module.exports = {
add: add,
isNumber: isNumber
};

Another possible solution is to use different source code preprocessors to make the private functions globally visible, but remove the attachment for production. For good example see blog post How to Unit Test Private Functions in JavaScript by Philip Walton.

I dislike both solutions. The first seems very brittle and pollutes the external interface. The second requires preprocessing step, or pollutes global object.

Test-mole

When trying to make inner functions externally visible (via exporting from closure or attaching to global object), we are trying to move then from their natural place into testing closure. This is shown in the next screenshot.

from closure

What if we moved a piece of testing framework inside the closure? This test-mole then has full access to the code around it, and can schedule tests right there.

to closure

The test-mole I implemented mimics BDD interface common to Jasmine and Mocha users. The mole has two modes:

  1. If the test mole finds globally defined BDD methods describe and it, the test mole knows it is testing mode, and thus points its methods to global methods. testMole.it then is just an alias to window.it.
  2. If the test mole does NOT find globally defined BDD methods, then it assumes we are running in production mode. In this case all mole's methods are noops.

For example, we can run the number tests just by loading add.js into mocha runner

add.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function isNumber(x) {
return typeof x === 'number';
}
testMole.it('checks numbers', function () {
console.assert(isNumber(4));
console.assert(!isNumber('4'));
});
function add(a, b) {
if (isNumber(a) && isNumber(b)) {
return a + b;
}
return 'arguments should be numbers';
};
module.exports = add;

We can run the unit tests by installing test-mole (using npm install test-mole) and then running via mocha.

$ mocha -R spec node_modules/test-mole/test-mole.js add.js
✓ checks numbers
1 passing (4ms)

test-mole benefits

These are highly subjective benefits of introducing test-mole into your environment.

  • Keeps testing code very close to the production code.
  • Keeps externally visible interfaces sparse and clean.
  • Increases code coverage and data types without multiple unit tests by targeting specific inputs.
  • Requires tiny single 3rd party script to work, no pre-processing is necessary to work in testing or production.
  • Can be a good and quick solution before code refactoring.
  • Avoids code duplication between code and testing specs.

The last code duplication point requires explaining. Often when trying to write a short and elegant functional callback, I find myself copying code from internal closure into separate spec file just so I can confirm it works. Being able to test the actual code without copying it (and keeping up to date!) is music to my ears.

test-mole shortcomings

There are multiple objections to test-mole. Feel free to write more objections as comments to this blog post.

  • Mixes testing code with production code leading to longer files. I tend to use test-mole for small unit tests, that should not significantly add to the source length.
  • The production bundle will include unit tests, making the page loading times longer. Compared to images, javascript code is usually small. If desired, you can remove test-mole specs using a preprocessor or javascript minifier with dead code removal, I hope.