3

I'm moving my JavaScript test code from Jest to Mocha. One nice feature of Jest is that it automatically generated stubs for your modules. These stubs implement the same API as the originals, but all their functions return undefined. Exported classes are also stubbed, i.e. they have all the same methods as the original, but the return undefined:

// mymodule.js
var MyClass = function() { this.foo = 42; };
MyClass.prototype.getFoo = function() { return this.foo; };
module.exports = {
  myclass: MyClass,
  myfunction: function() { return 42; }
};
// __tests__/mymodule-test.js
var mm = require('../mymodule');  // Jest auto-mocks this module.

describe('test', function() {
  it('functions and constructors are mocked', function() {
    expect(mm.myfunction()).toEqual(undefined);  // function is stubbed
    var mc = new mm.myclass();
    expect(mc.getFoo()).toEqual(undefined);  // fn on prototype is stubbed.
  });
});

For various reasons, I'm moving over to MochaJS but I'd like to keep this stubbing behavior. I can inject stubs for modules using proxyquire. But I need to define the stubs myself.

What I'd like is a function which which takes a Node module and returns something like the Jest auto-mocked version of the module. Jest's code to do this is in moduleMocker.js. I've written some code of my own to do this (included below). But it's quite tricky and doesn't feel like code I should be writing.

Is there a standard library for doing this?

Here's what I've written:

// stubber.js
var U = require('underscore');

function replaceFunctionsWithStubs(rootObj) {
  var replacedObjs = [];  // array of [original, replacement] pairs.

  function previousReplacement(obj) {
    for (var i = 0; i < replacedObjs.length; i++) {
      if (replacedObjs[i][0] == obj) {
        return replacedObjs[i][1];
      }
    }
    return null;
  }

  function replacer(obj) {
    var t = typeof(obj);
    if (t != 'function' && t != 'object') {
      // Simple data.
      return obj;
    }

    // Return previous mock to break circular references.
    var prevRep = previousReplacement(obj);
    if (prevRep) return prevRep;

    if (t == 'function') {
      var f = function() {};
      replacedObjs.push([obj, f]);
      if (!U.isEmpty(obj.prototype)) {
        // This might actually be a class. Need to stub its prototype, too.
        var newPrototype = replacer(obj.prototype);
        for (var k in newPrototype) {
          f.prototype[k] = newPrototype[k];
        }
      }

      // Stub any properties the function might have.
      for (var k in obj) {
        f[k] = replacer(obj[k]);
      }
      return f;
    } else if (typeof(obj) == 'object') {
      // TODO: Do I need to handle arrays differently?
      var newObj = {};
      replacedObjs.push([obj, newObj]);

      for (var k in obj) {
        newObj[k] = replacer(obj[k]);
      }
      return newObj;
    } else {
      return obj;  // string, number, null, undefined, ...
    }
  }

  return replacer(rootObj);
}

module.exports = function(m) {
  return replaceFunctionsWithStubs(m);
};
Community
  • 1
  • 1
danvk
  • 15,863
  • 5
  • 72
  • 116
  • I really feel Jest should have focused on doing JUST this. With another library to implement the rest of Jest's features. The reason I don't want to use Jest is because it forces you to use Jasmine etc – MrJD Jun 11 '15 at 02:13
  • My eventual solution to this issue was to [switch to Mocha](http://www.hammerlab.org/2015/02/14/testing-react-web-apps-with-mocha/) for testing. For a more recent React project, I've been using Mocha in-browser for testing. Once you're in-browser, tools like sinon work without the headaches mentioned in this question. – danvk Jun 12 '15 at 15:04
  • have you been able to find a way to automatically mock required modules? I know sinon can help, but to my knowledge it's not implicitly automatic – MrJD Jun 18 '15 at 07:23
  • You might be able to override `require` with a stub-generating version, but frankly I think automatic mocking is a bad idea. You should mock a whitelist of modules, rather than mock all but a blacklist of modules. Mocking everything means that internal refactors will break all your tests. – danvk Jun 19 '15 at 15:04

0 Answers0