I love playing around with Node require, see a couple of previous blog posts and existing projects:
- Hooking into Node loader for fun and profit
- Hacking Node require and really-need
- Faster Node app require and cache-require-paths
- Was NodeJS module used
Recently I had another epiphany: if I need to mock a module, why bother reading its source file from disk at all? Or how about loading fake or non-existing modules? Turns out this is very simple to do and can be used during unit testing and a few other fun things (stay tuned for the future updates).
Simple module mocking
Let us solve the first problem: imagine we really want to load something else instead of the actual
file foo.js
. We can insert the mock data first before the actual file had a chance to load.
Node has a cache of all loaded and evaluated code. Each entry is stored
under the fully resolved filename. The loaded source file foo.js
has something like this
1 | module.exports = 'this is foo'; |
1 | require('./foo.js') // loads "foo.js" and sticks it into the cache |
The only interesting properties that we need to insert ourselves are:
- id (same as the full resolved filename)
- exports - the actual compiled
module.exports
value - loaded - might not matter, but better to be set to
true
Thus we can insert the mock data directly into the require.cache
at desired key to use
completely fake module.
1 | var resolved = require('path').resolve('./foo.js'); |
Done. Any require('./foo.js')
after that will return the fake exports
value from the cache.
1 | module.exports = 'this is foo'; |
1 | // test-foo.js loads after insert-fake-foo.js |
Loading a non-existing module
Let us try loading a file called bar.js
that actually does not exist.
Typically this would be impossible: there would be an exception
Error: Cannot find module './bar.js'
at Function.Module._resolveFilename (module.js:331:15)
Even if we insert the mock data into the require.cache
,
the next call require('./bar.js')
still check if the file exists
in order to properly resolve it. Thus we need to fake the filename resolution. Luckily this is simple
to do.
1 | var resolvedFakeName = require('path').resolve('./bar.js'); |
We only return the fake yes answer for our non-existent module, and let the real method do the actual work for every other module. Combined with inserting mock data into the cache, this work around allows to load non-existent modules in the entire app.
Update 1
I hate to write an entire file Gruntfile.js
just to run a single grunt plugin, so I wrote
grunty - loads and runs a given plugin without global grunt CLI
or a gruntfile. For example to run the excellent
grunt-contrib-concat plugin, just add this to
the package.json (and install plugin and grunty as dependencies).
1 | "scripts": { |
In order to make this work, I avoided writing even a temporary Gruntfile.js
, instead
putting the configuration function directly into the module cache.
1 | // this returns initialized config function |
Pretty useful.