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.
1 | function isNumber(x) { |
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
1 | function isNumber(x) { |
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.
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.
The test-mole I implemented mimics BDD interface common to Jasmine and Mocha users. The mole has two modes:
- If the test mole finds globally defined BDD methods
describe
andit
, the test mole knows it is testing mode, and thus points its methods to global methods.testMole.it
then is just an alias towindow.it
. - 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
1 | function isNumber(x) { |
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.