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 couple of a potential issues: * 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 of require or import.
  • 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();
  });
});

Test passed

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 erquire 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.



Comments

comments powered by Disqus