There is a subtle issue for which, unfortunately, neither ES6 nor proxyquire provide a solution. It is well described in this stackoverflow.com answer. So far in the previous articles of the series, the dependencies we were mocking were in a separate module from the code we were unit testing. But what if they were in the same module?

Summary:

  • Neither proxyquire nor import * from provides a way to isolate and mock a dependency if it is located in the same module as the function which is being tested.
  • proxyquire is not designed for such application and throws an error.
  • In order for import * from to work we need to prepend  calls to mocked functions with export.. That is because babel places all the exported functions into export namespace while transpiling ES6 to ES5.

Suppose we have a module module3.js with two functions with one of the functions calling another one:

export const function1 = () => {
  return function2();
};

export const function2 = () => {
  return 'This is real function2';
};

We want to test function1 in isolation according to the purity principle and for that, we need to replace function2 with a mock. We do just that using the same technique as before and run the test:

'use strict';
import * as module3 from './module3';
import { function1 } from './module3';

/* global beforeEach, describe, sinon */

describe('import * from - a dependency in the same module', () => {
  let module3Mock;

  beforeEach(() => {
    module3Mock = sinon.mock(module3);
    module3Mock
      .expects('function2')
      .once()
      .returns('This is mocked function2');
  });

  it('function2 should be called', () => {
    console.log(function1());
    module3Mock.verify();
  });

  afterEach(() => {
    module3Mock.restore();
  });
});

Test failed

As you can see, the test failed, and the real function2 has been called instead of our mock. Why? To figure that out let's examine the code of module1 transpiled into ES5.

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var function1 = (exports.function1 = function function1() {
  return function2();
});

var function2 = (exports.function2 = function function2() {
  return 'This is real function2';
});

Here we can see that babel assigns two references to function2()exports.function2 and function2.  Then function2() is called from function 1() via function2 reference. Since they both point to the same funcrtions, it works as expected.

When we import module into our unit test, we actually import exports.function2 reference. Then we replace the function exports.function2 points to with our mocked-up version. But function1 reference inside module1 still stays intact and still points to the original (not mocked) function2().

It is like this:

Before function2() is mocked up:

Before mocking

After function2() is mocked up:

Before mocking

In order to fix that we need to change function2() call to exports.function2(). Then our code inside function1 will call our mocked version offunction2() instead of the original.

export const function1 = () => {
  return exports.function2();
};

export const function2 = () => {
  return 'This is real function2';
};

Test passed

Although you can do that every time you come across such a problem, I would be reluctant. It is a dirty hack, and I wouldn't make any dubious modifications to the code just to make unit tests pass.

But what about proxyquire? Will it help us in this case? Let's rewrite the test with proxyquire and see if we have more luck with it.

'use strict';
const proxyquire = require('proxyquire').noCallThru();

/* global beforeEach, describe, sinon */

describe('proxyquire - a dependency in the same module', () => {
  let module3
  let mock;

  beforeEach(() => {
    module3 = proxyquire('./module3');

    mock = sinon.mock(module3);
    mock
      .expects('function2')
      .once()
      .returns('This is mocked function2');
  });

  it('function2 function should be called', () => {
    module3.function1();
    mock.verify();
  });

  afterEach(() => {
    mock.restore();
  });
});

Test passed

Not quite. Proxyquire was designed to mock module dependencies but not the modules themselves. Turns out we can't use it if the dependencies are in the same module.

So, that is it. Both ES6's import * from... and proxyquire provide convenient ways to mock Javascript dependencies. But no single approach is perfect. They all have their own features and limitations. I personally use import * from... whenever possible just because it doesn't require any 3rd party libraries, but take advantage of proxyquire's ability to disable the evaluation of dependency modules whenever I need that feature.

The source code of the examples for this article can be downloaded from my Github repository.



Comments

comments powered by Disqus