Mocking ES6 module dependencies with proxyquire
In part one, I wrote about mocking ES6 module dependencies using the ES6 native import * from
construct. It works mostly fine. However, you need to be aware of a potential issue:
- You need to import modules to mock them, which means that those modules will be evaluated. That may be a problem if you don’t want the code in this modules to be executed.
Another method I found to work well is using proxyquire. It is one of many libraries aiming to streamline mocking dependencies to simplify unit testing.
Summary:
- proxyquire is a 3rd party library which enables mocking Javascript (and ES6) module dependencies by importing them via
proxyquire
instead ofrequire
orimport
. - It provides an feature to disable the evaluation of all or some of the mocked modules via its
noCallThru
option.
Let’s look at the same examples as in part one and rewrite their unit tests with proxyquire.
Just like in the import * as
example, we have a module, this time it is export2.js
which exports a single function:
console.log('If you can see that, export2.js is evaluated');
export const exportFunc = () => {
return 'This is real exportFunc from export2';
};
Again, if we see the message printed by the console.log
statement, than means this module is evaluated by the unit test.
module2
module imports and calls the function exported from export2.js
:
import { exportFunc} from './export2';
export const myFunc = () => {
return exportFunc();
};
Our unit test for module2
which mocks export2
with proxyquire looks like this:
'use strict';
// We using proxyquire with noCallThru option here
// to stop index module from being evaluated.
const proxyquire = require('proxyquire').noCallThru();
/* global beforeEach, describe, sinon */
describe.only('proxyquire - a dependency in another module', () => {
let module2;
let export2Mock;
let export2 = {
exportFunc: function() {}
};
beforeEach(() => {
export2Mock = sinon.mock(export2);
export2Mock
.expects('exportFunc')
.once()
.returns('This is mocked exportFunc');
module2 = proxyquire('./module2', {
'./export2': export2
});
});
it('exportFunc function should be called', () => {
module2.myFunc();
export2Mock.verify();
});
afterEach(() => {
export2Mock.restore();
});
});
The test passed, our mocked-up exportFunc
was called, and, since we didn’t see the message printed by console.log
, the code of export2
module was not evaluated.
Now, let’s figure out what’s going on in the unit test step by step.
const proxyquire = require('proxyquire').noCallThru();
Here we import proxyquire into our project. noCallThru()
here indicates that the module dependencies should not be evaluated.
I used require
here to make noCallThru()
a part of the import, which is more convenient to do with require
than ES6 import
.
Then we create an object representing our export2
module.
let export2 = {
exportFunc: function() {}
};
After that, we create a sinon mock of our export2
module. We expect exportFunc
to be called once and it returns string 'This is mocked exportFunc'
.
The reason why we need to create export2
object above is because sinon mocks can only be created on existing objects.
export2Mock = sinon.mock(export2);
export2Mock
.expects('exportFunc')
.once()
.returns('This is mocked exportFunc');
Next, we import module2
into our test using proxyquire, and we specify that its ./export2.js
dependency must be substituted by export2
created above. Note that the substituted dependencies are specified by their path rather than module name. Unfortunately, we can’t use require
or import
here.
module2 = proxyquire('./module2', {
'./export2': export2
});
Finally, we call myFunc
from the module we imported via proxyquire and verify that the sinon mock’s conditions are met.
it('exportFunc function should be called', () => {
module2.myFunc();
export2Mock.verify();
});
And, as the last step, we reset the sinon mock.
afterEach(() => {
export2Mock.restore();
});
As you can see, proxyquire provides features for mocking-up Javascript modules similar to ES6 import * as...
construction, it also allows disabling the evaluation of real dependency modules via noCallThru()
option. Although in my example I specified noCallThru
at the import level, it also can be applied to the individual dependencies like this:
const proxyquire = require('proxyquire');
...
let export2 = {
exportFunc: function() {},
'@noCallThru': true
};
...
module2 = proxyquire('./module2', {
'./export2': export2
});
However, there is one issue for which neither import * as
nor proxyquire provides a solution. I will examine it in the next article.
The source code of the examples for this article can be downloaded from my Github repository.