Optionally exporting modules for testing in NodeJS

Phil
toJS
Published in
3 min readMar 20, 2018

Click here for the TL;DR

We recently worked on an application that was composed of several long-running Node processes managed with PM2. The top-level processes/modules were not written with testing in mind and were (as you’d expect for a Node entry point) immediately executed upon require. This poses some issues when trying to test the code within these files — we were unable to easily control when the code was executed and how the dependencies were constructed.

There are a few solutions to this problem, for example:

  1. Keep the contents of those files to an absolute minimum (by delegating behaviour to other modules) so there’s little-to-no value in testing them.
  2. Use Proxyquire to do a fresh require of the module inside each test.
  3. Detect if the module is being executed as a top-level process, and if not, export it.

(1) is usually enough. (2) is a suitable solution, however things get complicated if you want to manage (and stub/spy on) dependencies of the module. (3) is potentially writing code for the sake of tesing, but in our particular case it actually reduced the amount of complicated stubbing of the module’s dependencies. Also, it’s fun to do so let’s see how.

A trivial example

A Node application entry point might look something like this…

Which doesn’t seem all that bad. But if you wanted to stub/spy/mock the instance of SomeDependency passed to the constructor, you’d have to do something like this…

On a typical project with good test coverage you might expect to see a test-to-source code ratio of 2 or 3:1, which means you’re giving yourself (and your fellow engineers) a lot of unnecessary additional boilerplate to write.

Node modules

When a module is require’d, Node actually wraps the file contents in a function which gives the code access to a few useful variables: exports, require, module, __filename and __dirname. The require function has a handy property main which is a reference to the module that was used as the entry point for the currently running Node process.

To determine if the current module is being run as the root Node process, you just need to do this:

const isNodeProcess = require.main === module

So, to optionally export the module or execute it, you just need to do something like…

Your test case now looks a lot less complicated (and more likely a regular unit test)…

TL;DR

require.main === module when a module is the entry point of a Node process. If you would rather export something when the file is loaded via a require statement, simply do so when that statement is false.

You can read more about the way Node modules work here.

As always, if you found this useful give us a shout out on Twitter, and if you are looking for some help on a JavaScript project, hit us up @ https://toJS.io.

--

--