最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to stub require()expect calls to the "root" function of a module? - Stack Overflow

programmeradmin1浏览0评论

Consider the following jasmine spec:

describe("something.act()", function() {
  it("calls some function of my module", function() {
    var mod = require('my_module');
    spyOn(mod, "someFunction");
    something.act();
    expect(mod.someFunction).toHaveBeenCalled();
  });
});

This is working perfectly fine. Something like this makes it green:

something.act = function() { require('my_module').someFunction(); };

Now have a look at this one:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = require('my_module');
    spyOn(mod); // jasmine needs a property name
                // pointing to a function as param #2
                // therefore, this call is not correct.
    something.act();
    expect(mod).toHaveBeenCalled(); // mod should be a spy
  });
});

This is the code I'd like to test with this spec:

something.act = function() { require('my_module')(); };

This has bogged me down several times in the last few months. One theoretical solution would be to replace require() and return a spy created with createSpy(). BUT require() is an unstoppable beast: it is a different "copy" of the function in each and every source file/module. Stubbing it in the spec won't replace the real require() function in the "testee" source file.

An alternative is to add some fake modules to the load path, but it looks too complicated to me.

Any idea?

Consider the following jasmine spec:

describe("something.act()", function() {
  it("calls some function of my module", function() {
    var mod = require('my_module');
    spyOn(mod, "someFunction");
    something.act();
    expect(mod.someFunction).toHaveBeenCalled();
  });
});

This is working perfectly fine. Something like this makes it green:

something.act = function() { require('my_module').someFunction(); };

Now have a look at this one:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = require('my_module');
    spyOn(mod); // jasmine needs a property name
                // pointing to a function as param #2
                // therefore, this call is not correct.
    something.act();
    expect(mod).toHaveBeenCalled(); // mod should be a spy
  });
});

This is the code I'd like to test with this spec:

something.act = function() { require('my_module')(); };

This has bogged me down several times in the last few months. One theoretical solution would be to replace require() and return a spy created with createSpy(). BUT require() is an unstoppable beast: it is a different "copy" of the function in each and every source file/module. Stubbing it in the spec won't replace the real require() function in the "testee" source file.

An alternative is to add some fake modules to the load path, but it looks too complicated to me.

Any idea?

Share Improve this question asked Aug 9, 2011 at 14:06 jbprosjbpros 1,6571 gold badge15 silver badges17 bronze badges
Add a comment  | 

6 Answers 6

Reset to default 6

rewire is awesome for this

var rewire = require('rewire');

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = rewire('my_module');
    var mockRootFunction = jasmine.createSpy('mockRootFunction');
    var requireSpy = {
      mockRequire: function() {
        return mockRootFunction;
      }
    };
    spyOn(requireSpy, 'mockRequire').andCallThrough();

    origRequire = mod.__get__('require');
    mod.__set__('require', requireSpy.mockRequire);

    something.act();
    expect(requireSpy.mockRequire).toHaveBeenCalledWith('my_module');
    expect(mockRootFunction).toHaveBeenCalled();

    mod.__set__('require', origRequire);
  });
});

It looks like I found an acceptable solution.

The spec helper:

var moduleSpies = {};
var originalJsLoader = require.extensions['.js'];

spyOnModule = function spyOnModule(module) {
  var path          = require.resolve(module);
  var spy           = createSpy("spy on module \"" + module + "\"");
  moduleSpies[path] = spy;
  delete require.cache[path];
  return spy;
};

require.extensions['.js'] = function (obj, path) {
  if (moduleSpies[path])
    obj.exports = moduleSpies[path];
  else
    return originalJsLoader(obj, path);
}

afterEach(function() {
  for (var path in moduleSpies) {
    delete moduleSpies[path];
  }
});

The spec:

describe("something.act()", function() {
  it("calls the 'root' function of my module", function() {
    var mod = spyOnModule('my_module');
    something.act();
    expect(mod).toHaveBeenCalled(); // mod is a spy
  });
});

This is not perfect but does the job quite well. It does not even mess with the testee source code, which is kind of a criterion for me.

I needed to do this today and came across this post. My solution follows:

In a spec helper:

var originalRequire = require;
var requireOverrides = {};

stubModule = function(name) {
  var double = originalRequire(name);
  double['double'] = name;
  requireOverrides[name] = double;
  return double;
}

require = function(name) {
  if (requireOverrides[name]) {
    return requireOverrides[name];
  } else {
    return originalRequire(name);
  }
}

afterEach(function() {
  requireOverrides = {};
});

In a spec:

AWS = stubModule('aws-sdk');
spyOn(AWS.S3, 'Client');

// do something

expect(AWS.S3.Client).toHaveBeenCalled();

This was very helpful, but it doesn't support calling through via .andCallThrough().

I was able to adapt it though, so I thought I'd share:

function clone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  var key;
  var temp = new obj.constructor();
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = clone(obj[key]);
    }
  }
  return temp;
};

spyOnModule = function spyOnModule(name) {
  var path          = require.resolve(name);
  var spy           = createSpy("spy on module \"" + name + "\"");
  moduleSpies[path] = spy;

  // Fake calling through
  spy.andCallThrough = function() {

    // Create a module object
    var mod = clone(module);
    mod.parent = module;
    mod.id = path;
    mod.filename = path;

    // Load it backdoor
    originalJsLoader(mod, path);

    // And set it's export as a faked call
    return this.andCallFake(mod.exports);
  }

  delete require.cache[path];
  return spy;
};

You can use gently module (https://github.com/felixge/node-gently). Hijacking require is mentioned in examples, and dirty NPM module actively uses it, so I suppose it works.

There is another approach. You can put the module in the global scope by not using var when requiring it:

someModule = require('someModule');

describe('whatever', function() {
  it('does something', function() {
    spyOn(global, 'someModule');

    someFunctionThatShouldCallTheModule();

    expect(someModule).toHaveBeenCalled();
  }
}

You could also wrap the module in another module:

//someModuleWrapper.js
require('someModule');

function callModule(arg) {
  someModule(arg);
}
exports.callModule = callModule;

//In the spec file:
someModuleWrapper = require('someModuleWrapper');

describe('whatever', function() {
  it('does something', function() {
    spyOn(someModuleWrapper, 'callModule');

    someFunctionThatShouldCallTheModule();

    expect(someModuleWrapper.callModule).toHaveBeenCalled();
  }
}

And then obviously make sure that wherever someFunctionThatShouldCallTheModule is, you're requiring the wrapper rather than the real module.

发布评论

评论列表(0)

  1. 暂无评论