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 | var foo = require('foo'); // returns a function |
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 | function isPrimitive(x) { |
The unit tests would do something like this
1 | var foo = require('foo'); |
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.
1 | var used = {}; |
Before loading the tests, let us preload all 3rd party modules
1 | var proxyExports = require('./spy-on-module'); |
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 | var proxyExports = require('./spy-on-module'); |
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.