Was NodeJS module used

How to determine if a dependency module was actually used?

When we list module's dependencies in the package.json, we let everyone to believe that the module actually uses them. During testing we might use only a few of them. If we upgrade depedencies by running unit tests, we might incorrectly believe that we actually tested the 3rd party module before upgrading its version to a newer one.

For example, let us take a small module that requires two other modules, but only uses one of them.

1
2
3
4
5
6
7
var foo = require('foo'); // returns a function
console.log('foo returned', foo());
var bar = require('bar'); // returns a function
if (!test) {
bar();
}
// bar is not used during tests

Without knowing that bar is never called, we might upgrade bar to a new version, completely breaking our code. Typically we use code coverage to determine if a piece of code is used or not. When dealing with 3rd party dependencies we do NOT want to instrument and look at the code coverage.

Instead we can spy on the 3rd party module and mark module used if:

  • it returns a primitive value, like a string, number, boolean
    • object with primitive properties
  • it returns a function that is executed
  • it returns an object with methods, and at least a single method is executed

To tell if a module has been used, we will run our unit tests, while spying on each 3rd party module. We can implement this spying in 2 ways.

Overwrite the main script

Whenever we require a 3rd party module, Node loads its main script using value specified in the package.json (or default index.js). We can replace main script with our index.js that points at the correct script.

our code
node_modules/
    foo/
        index.js // our code
        index_bak.js // foo's own index.js
    bar/
        index.js // our code
        index_bak.js // bar's own index.js

index_bak.js contains the original script from the module foo. We will copy it back to index.js after testing. index.js contains our proxying code. For simplicity, I omit proxying objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function isPrimitive(x) {
return typeof x === 'string' ||
typeof x === 'number' ||
typeof x === 'boolean';
}
function proxyIndex(exported) {
la(check.unemptyString(filename), 'missing loaded filename');
exported.__used = false;
if (isPrimitive(exports)) {
exported.__used = true;
return exported;
}
if (typeof exported === 'function') {
return function proxyFn() {
exported.__used = true;
return exports.apply(null, arguments);
};
}
// everything else
return exported;
}
var exported = require('./index_bak'); // true exports
module.exports = proxyIndex(exported);

The unit tests would do something like this

1
2
3
4
5
6
7
var foo = require('foo');
var bar = require('bar');
console.assert(foo() === 'foo', 'foo is working');
// bar has not been tested
// then check if each module has been tested
console.log('foo was tested?', require('foo').__used);
console.log('bar was tested?', require('bar').__used);

Then we can restore each index_bak.js back to its original filename index.js.

Spying on module's use

The above approach requires a lot of file shuffling. I prefer a different approach using my own require function hack really-need. This hack wraps built-in require with several options, including busting cache and modifying the exported values. We can take write just a single file ourselves that will proxy every returned value for 3rd party module names.

spy-on-module.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var used = {};
function proxyExports(exports, filename) {
if (typeof used[filename] === 'undefined') {
used[filename] = false;
}
if (isPrimitive(exports)) {
used[filename] = true;
return exports;
}
if (typeof exports === 'function') {
return function proxyFn() {
used[filename] = true;
return exports.apply(null, arguments);
};
}
return exports;
}
proxyExports.used = used;
module.exports = proxyExports;

Before loading the tests, let us preload all 3rd party modules

1
2
3
4
5
6
7
8
9
var proxyExports = require('./spy-on-module');
require = require('really-need');
var names = Object.key(require('package.json').dependencies);
names.forEach(function (name) {
require(name, {
bust: true,
post: proxyExports
});
});

This loads and proxies every 3rd party module listed in the dependencies object. Then start unit tests as usual. At the end, inspect the used property

1
2
3
var proxyExports = require('./spy-on-module');
// unit tests
console.log(proxyExports.used);

It should print something like this

{ '/test/node_modules/foo/index.js': true,
  '/test/node_modules/bar/index.js': false }

Thus we can say that even if the unit tests pass successfully, we cannot upgrade bar dependency - it was never tested.

I created tiny utility was-it-used with this functionality.