Question

I've been struggling with this now for several hours and I'm not getting anywhere. This is a simplified version of the code I'm working with:

I have a module called 'setup' that internally reads files from the filesystem, and does some stuff. I'm attempting to write some unit tests that use stubs to return fakedata rather than read from the file system. I'm using the sandbox feature of nodeunit since the functions that I am stubbing are not exported.

setup
├── data.js
├── index.js
├── loader.js
├── package.json
└── test
    └── test-setup.js

index.js:

var
  loader = require( './loader' );

module.exports = function () {
  // do stuff
  loader( function ( data ) {
    console.log( "read from file: " + JSON.stringify( data, null, 2 ));
    // do more stuff
    return data;
  });
};

loader.js:

module.exports = function ( cb ) {
  var data = require( './data' );
  cb( data );
};

data.js:

module.exports = {
  data: {
    field1: "value1",
    field2: "value2"
  }
};

test-setup.js:

var
  nodeunit = require( 'nodeunit' ),
  sandbox  = require( 'nodeunit' ).utils.sandbox,
  sinon    = require( 'sinon' ),
  loader   = require( '../loader' ),
  setup    = require( '..' );

exports[ 'setup file loading' ] = nodeunit.testCase ({
  setUp: function ( callback ) {
    var
      boxModule = { exports: {} },
      boxGlobals = {
        module  : boxModule,
        exports : boxModule.exports,
        loader  : loader,
        require : function () { return loader; },
        console : console
      };
    this.fakedata = {
      fakedata: {
        field1: "value1",
        field2: "value2"
      }
    };
    this.sandbox = sandbox( '../index.js', boxGlobals );
    callback();
  },

  tearDown: function ( callback ) {
    delete this.sandbox;
    callback();
  },

  'test1: setup by loading the file normally': function ( t ) {
    t.ok( setup() === undefined, 'data is undefined - has not loaded yet' );
    t.done();
  },

  'test2: setup using sandbox and loader function replaced with stub': function ( t ) {
    var fakedata = this.fakedata;
    var returnFakeData = function () {
      return fakedata;
    };
    var stub = sinon.stub( this.sandbox, 'loader', returnFakeData);
    t.equal( this.sandbox.module.exports(), this.fakedata, 'returned value is the fakedata from the stub' );
    t.done();
  }

});

Test 1 passes. Test 2 fails with: AssertionError: returned value is the fakedata from the stub

The log messages show that it has printed the data from the file rather than the fakedata. When I inspect the this.sandbox.loader function in the debugger it is correctly set to the stub.

The second issue is that I had to fake out the require and pass in the loader function with:

require : function () { return loader; },

Otherwise require couldn't find the ./loader. I tried to change directory with process.chdir before the test, but the require still failed even when the cwd was correctly set to the project directory. Obviously faking out require would only work if there is 1 module required, in the real code there are several requires. The only other way I got the ./loader to load was to modify the code to use an absolute path, and editing the code each time to run the test isn't feasible.

I've probably missed something obvious, writing unit tests for module code that loads data asynchronously must be pretty standard stuff. Is sandbox/stubbing the right approach?

Any help much appreciated.

Was it helpful?

Solution

After quite a lot of insanity I came up with a solution using proxyquire which "Proxies nodejs require in order to allow overriding dependencies during testing". Much better than using a nodeunit sandbox.

I also had to update the module to accept a callback function, and modify loader.js to export the function as 'load'.

Hopefully this will be useful to someone out there.

index.js:

var
  loader = require( './loader' );

module.exports = function ( file, cb ) {
  // do stuff
  loader.load( file, function ( data ) {
    console.log( "read from file: " + JSON.stringify( data, null, 2 ));
    // do more stuff
    cb ( data );
  });
};

loader.js:

module.exports = {
  load: function ( file, cb ) {
    var data = require( file );
    cb( data );
  }
};

test-setup.js:

var
  proxyquire = require( 'proxyquire' ),
  sinon      = require( 'sinon' ),
  pathStub   = {},
  fakedata   = {
    fakedata: {
      field1: "fakevalue1",
      field2: "fakevalue2"
    }
  },
  setup = proxyquire('..', {
    './loader': pathStub
  });

exports.testSetup = function ( t ) {
  pathStub.load = sinon.stub();
  pathStub.load.yields( fakedata );

  t.expect( 1 );
  setup( './data', function ( data ) {
    t.equal( data, fakedata, 'setup returned fakedata' );
    t.done();
  });
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top