Sunday, December 13, 2015

Instructing Proxyquire To Ignore Nested Requires

When writing unit tests for Node.js applications, you can use the proxyquire npm module to override the modules pulled in by the file under test using require(), replacing them with your own.  So say your file containing the methods you want to test pulls in two other modules using require:


//underTest.js
var moduleA = require( './moduleA' );
var master = require( '../../core/master' );
...
module.exports = {
  runFoo: function() { return moduleA.outputResult() }
};

...in your unit test, you can use a stub library like Sinon in conjunction with proxyquire to instantiate the file under test but with the stubs used in place of the normal modules:


//underTest.spec.js
var sinon = require( 'sinon' );
var proxyquire = require( 'proxyquire' );

describe( 'underTest.js functions', function() {
  var stubModuleA, stubMaster;

  before( function() {

    var stubModuleA = sinon.stub();
    var stubMaster = sinon.stub();

    underTest = proxyquire( './underTest', {
      './moduleA': stubModuleA,
      '../../core/master': stubMaster
    });
  
  });
  
});

...and then you can spy on and define the function behavior of the stubbed objects for your tests.

When I first did this, I ran into problems because one of the modules I was replacing (say moduleA) itself pulled in a module of its own:


//moduleA.js
var main = require( '../../main' );

...and in that main module were functions that executed as soon as the file was loaded via require(), and the execution of those functions caused the before() block of my test file to bomb.

After some research, I discovered that one solution was to use proxyquire to override the require() call in moduleA, and instruct proxyquire to essentially ignore the require by not calling through to it.


//underTest.spec.js (revised)
var sinon = require( 'sinon' );
var proxyquire = require( 'proxyquire' );

describe( 'underTest.js functions', function() {
  var stubModuleA, stubMaster;

  before( function() {

    var stubModuleA = proxyquire( './moduleA', {
      '../../main': { '@noCallThru': true }
    });
    
    var stubMaster = sinon.stub();

    underTest = proxyquire( './underTest', {
      './moduleA': stubModuleA,
      '../../core/master': stubMaster
    });
  
  });
  
});