8

I am struggling to write high quality tests around my node modules. The problem is the require module system. I want to be able to check that a certain required module has a method or its state has changed. There seem to be 2 relatively small libraries which can be used here: node-gently and mockery. However, due to their low 'profile' it makes me think that either people don't test this, or there is another way of doing this that I am not aware of.

What is the best way to mock out and test a module that has been required?

mshell_lauren
  • 5,171
  • 4
  • 28
  • 36
henry.oswald
  • 5,304
  • 13
  • 51
  • 73

3 Answers3

12

----------- UPDATE ---------------

node-sandbox works on the same principals as stated below but is wrapped up in a nice module. I am finding it very nice to work with.


--------------- detailed awnser ---------------

After much trial I have found the best way to test node modules in isolation while mocking things out is to use the method by Vojta Jina to run each module inside of a vm with a new context as explained here.

with this testing vm module:

var vm = require('vm');
var fs = require('fs');
var path = require('path');

/**
 * Helper for unit testing:
 * - load module with mocked dependencies
 * - allow accessing private state of the module
 *
 * @param {string} filePath Absolute path to module (file to load)
 * @param {Object=} mocks Hash of mocked dependencies
 */
exports.loadModule = function(filePath, mocks) {
  mocks = mocks || {};

  // this is necessary to allow relative path modules within loaded file
  // i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
  var resolveModule = function(module) {
    if (module.charAt(0) !== '.') return module;
    return path.resolve(path.dirname(filePath), module);
  };

  var exports = {};
  var context = {
    require: function(name) {
      return mocks[name] || require(resolveModule(name));
    },
    console: console,
    exports: exports,
    module: {
      exports: exports
    }
  };

  vm.runInNewContext(fs.readFileSync(filePath), context);
  return context;
};

it is possible to test each module with its own context and easily stub out all external dependencys.

fsMock = mocks.createFs();
mockRequest = mocks.createRequest();
mockResponse = mocks.createResponse();

// load the module with mock fs instead of real fs
// publish all the private state as an object
module = loadModule('./web-server.js', {fs: fsMock});

I highly recommend this way for writing effective tests in isolation. Only acceptance tests should hit the entire stack. Unit and integration tests should test isolated parts of the system.

henry.oswald
  • 5,304
  • 13
  • 51
  • 73
  • Thanks for the great answer ! However I have another question : I wasn't able to leverage this technique to override private functions with my own custom ones in the module being tested. it results in two functions : one in the local scope and another one in the global scope, with the module under test always calling the global/original version. – Attilah Dec 13 '14 at 23:24
5

I think the mockery pattern is a fine one. That said, I usually opt to send in dependencies as parameters to a function (similar to passing dependencies in the constructor).

// foo.js
module.exports = function(dep1, dep2) {
    return {
        bar: function() {
            // A function doing stuff with dep1 and dep2
        }
    }
}

When testing, I can send in mocks, empty objects instead, whatever seems appropriate. Note that I don't do this for all dependencies, basically only IO -- I don't feel the need to test that my code calls path.join or whatever.

I think the "low profile" that is making you nervous is due to a couple of things:

  • Some people structure their code similar to mine
  • Some people have their own helper fulfilling the same objective as mockery et al (it's a very simple module)
  • Some people don't unit test such things, instead spinning up an instance of their app (and db, etc) and testing against that. Cleaner tests, and the server is so fast it doesn't affect test performance.

In short, if you think mockery is right for you, go for it!

Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
1

You easily mock require by using "a": https://npmjs.org/package/a

//Example faking require('./foo') in unit test:
var fakeFoo = {};
var expectRequire = require('a').expectRequire;
expectRequire('./foo).return(fakeFoo);


//in sut:
var foo = require('./foo); //returns fakeFoo
Soroush Mirzaei
  • 936
  • 2
  • 19
  • 34