Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Node.js, mock out and test a module that has been required?

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?

like image 493
henry.oswald Avatar asked Mar 05 '12 09:03

henry.oswald


2 Answers


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

like image 81
henry.oswald Avatar answered Sep 20 '22 17:09

henry.oswald


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!

like image 20
Linus Thiel Avatar answered Sep 23 '22 17:09

Linus Thiel