How to stub/spy a default exported function?
By: Mina Luke
Tags: Commonjs, Export, Stub, Sinon, JavaScript

In different words, how to stub/spy on a module that has only one function exported?

// sum.js module function sum(x, y) {

return x + y;

} module.exports = sum;

Let’s say the above function is imported in a js file as follows:

// doStuff.js module const sum = require('./sum'); function doStuff(x, y) {

// do some stuff then ...

return sum(x, y);

} module.exports = doStuff;

What we need to do here is testing doStuff function while stubbing the sum function.

Is that possible? Well, yes. However it is not straight forward . I am writing this story to explain why it is not straight forward and what is the possible options or tools we can use to do that.

First Solution

>>>>>>>>

If you are the creator of that sum function then the simple solution here is to modify it and change the way the function is exported to the one of the following solutions:

module.exports.sum = sum; // or module.exports = { sum }; // or sometimes we might find it like module.exports.default = sum;

Here we are changing the sum module to export an object that contains the sum function.

Then you may write stubs using Sinon as follow:

const { assert } = require('chai');

const sinon = require('sinon'); const sumModule = require('./sum');

const doStuff = require('./doStuff'); sinon.stub(sumModule, 'sum').returns('fake sum') const actual = doStuff(1, 2); assert.equals(actual, 'fake sum');

But what if I do not have control over the sum module to change it? What if I installed this utility function from an open source library and you can not change it? Well, you may consider contributing to the open source library. Or…

Here is the second option you can do.

Second Solution

>>>>>>>>>>

Install proxyquire or mock-require. Yeah as you heard it you need to install a library just to stub a default exported function.

const proxyquire = require('proxyquire')

const sinon = require('sinon'); const sum = sinon.stub(); const moduleWithDependency = proxyquire('./doStuff’, { './sum': sum, }); moduleWithDependency(1, 2); sinon.assert.calledOnce(sum);

Is that all? No. There is another solution to do without installing any extra libraries. That’s why I am writing this story.

Third Solution

>>>>>>>>

Let’s first understand the issue we are trying to fix here. Node import modules once and then cache it. Once cached it does not import them again.

You can try this easily by doing the following.

In the start of any JS file write:

// module name: test-module.js

console.log('module loaded'); // some other code here

and require that file twice in another file.

require('./test-module');

require('./test-module');

You would see the console only prints `module loaded` one time not two times.

Why?

This is how Node is working. Once the app is started node would load exported modules once and cache them in this object module.cache. It is where Node keeps track of all commonjs modules loaded.

Fortunately this object is writable so we can edit or remove loaded modules as follows:

delete module.cache[require.resolve(module-name)];

You may verify this by requiring the same module twice again but this time write the delete statement in the middle as follows

require('module-name'); delete module.cache[require.resolve('module-name')]; require('module-name');

You would see the console prints ‘module loaded’ twice this time.

What is the relation between this module loading thing and stubbing a default exported function?

Node would load and cache the default exported function so no stubbing library like sinon would be able to fake/spy it unless we reload the module again in the cache object. How?

// First we need to remove the doStuff module

delete require.cache[require.resolve('./doStuff')]; // Second we need rewrite the cached sum module to be as follows:

require.cache[require.resolve('./sum')] = {

exports: sinon.stub(),

}; // Third we need to require the doStuff module again

require('./doStuff');

And that’s it.

But what if the function I am testing is calling 2 or more default function from different modules? I want to stub all these default exported functions.

Well, in this case lets rewrite the above three lines in a function that automate this for us as follows:

function fakeDefaultExport(moduleRelativePath, stubs) => { if (require.cache[require.resolve(moduleRelativePath)]) {

delete require.cache[require.resolve(moduleRelativePath)];

} Object.keys(stubs).forEach(dependencyRelativePath => {

require.cache[require.resolve(dependencyRelativePath)] = {

exports: stubs[dependencyRelativePath],

};

});



return require(moduleRelativePath); };

… and we can use it as follows:

const doStuff = fakeDefaultExport("./doStuff", {

'./sum': sinon.stub().returns('fake adding'),

'./sub': sinon.stub().returns('fake subtraction'),

});

Hope you like it.