164
votes

This is a trivial example that illustrates the crux of my problem:

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;

I am trying to write a unit test for this code. How can I mock out the requirement for the innerLib without mocking out the require function entirely?

So this is me trying to mock out the global require and finding out that it won’t work even to do that:

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};

The problem is that the require function inside the underTest.js file has actually not been mocked out. It still points to the global require function. So it seems that I can only mock out the require function within the same file I’m doing the mocking in. If I use the global require to include anything, even after I’ve overridden the local copy, the files being required will still have the global require reference.

8
you have to overwrite global.require. Variables write to module by default as modules are module scoped.Raynos
@Raynos How would I do that? global.require is undefined? Even if I replace it with my own function other functions would never use that would they?HMR

8 Answers

186
votes

You can now!

I published proxyquire which will take care of overriding the global require inside your module while you are testing it.

This means you need no changes to your code in order to inject mocks for required modules.

Proxyquire has a very simple api which allows resolving the module you are trying to test and pass along mocks/stubs for its required modules in one simple step.

@Raynos is right that traditionally you had to resort to not very ideal solutions in order to achieve that or do bottom-up development instead

Which is the main reason why I created proxyquire - to allow top-down test driven development without any hassle.

Have a look at the documentation and the examples in order to gauge if it will fit your needs.

118
votes

A better option in this case is to mock methods of the module that gets returned.

For better or worse, most node.js modules are singletons; two pieces of code that require() the same module get the same reference to that module.

You can leverage this and use something like sinon to mock out items that are required. mocha test follows:

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});

Sinon has good integration with chai for making assertions, and I wrote a module to integrate sinon with mocha to allow for easier spy/stub cleanup (to avoid test pollution.)

Note that underTest cannot be mocked in the same way, as underTest returns only a function.

Another option is to use Jest mocks. Follow up on their page

12
votes

I use mock-require. Make sure you define your mocks before you require the module to be tested.

2
votes

Mocking require feels like a nasty hack to me. I would personally try to avoid it and refactor the code to make it more testable. There are various approaches to handle dependencies.

1) pass dependencies as arguments

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}

This will make the code universally testable. The downside is that you need to pass dependencies around, which can make the code look more complicated.

2) implement the module as a class, then use class methods/ properties to obtain dependencies

(This is a contrived example, where class usage is not reasonable, but it conveys the idea) (ES6 example)

const innerLib = require('./path/to/innerLib')

class underTestClass {
    getInnerLib () {
        return innerLib
    }

    underTestMethod () {
        return this.getInnerLib().doComplexStuff()
    }
}

Now you can easily stub getInnerLib method to test your code. The code becomes more verbose, but also easier to test.

2
votes

If you've ever used jest, then you're probably familiar with jest's mock feature.

Using "jest.mock(...)" you can simply specify the string that would occur in a require-statement in your code somewhere and whenever a module is required using that string a mock-object would be returned instead.

For example

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

would completely replace all imports/requires of "firebase-admin" with the object you returned from that "factory"-function.

Well, you can do that when using jest because jest creates a runtime around every module it runs and injects a "hooked" version of require into the module, but you wouldn't be able to do this without jest.

I have tried to achieve this with mock-require but for me it didn't work for nested levels in my source. Have a look at the following issue on github: mock-require not always called with Mocha.

To address this I have created two npm-modules you can use to achieve what you want.

You need one babel-plugin and a module mocker.

In your .babelrc use the babel-plugin-mock-require plugin with following options:

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

and in your test file use the jestlike-mock module like so:

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

The jestlike-mock module is still very rudimental and does not have a lot of documentation but there's not much code either. I appreciate any PRs for a more complete feature set. The goal would be to recreate the whole "jest.mock" feature.

In order to see how jest implements that one can look up the code in the "jest-runtime" package. See https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 for example, here they generate an "automock" of a module.

Hope that helps ;)

1
votes

You can't. You have to build up your unit test suite so that the lowest modules are tested first and that the higher level modules that require modules are tested afterwards.

You also have to assume that any 3rd party code and node.js itself is well tested.

I presume you'll see mocking frameworks arrive in the near future that overwrite global.require

If you really must inject a mock you can change your code to expose modular scope.

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}

Be warned this exposes .__module into your API and any code can access modular scope at their own danger.

1
votes

You can use mockery library:

describe 'UnderTest', ->
  before ->
    mockery.enable( warnOnUnregistered: false )
    mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
    @underTest = require('./path/to/underTest')

  it 'should compute complex value', ->
    expect(@underTest()).to.eq 'Complex result'
1
votes

Simple code to mock modules for the curious

Notice the parts where you manipulate the require.cache and note require.resolve method as this is the secret sauce.

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}

Use like:

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})

BUT... proxyquire is pretty awesome and you should use that. It keeps your require overrides localized to tests only and I highly recommend it.