如何存根要求()/期望调用模块的“根”function?

考虑下面的茉莉花规格:

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(); }); }); 

这工作得很好。 像这样的东西使它变成绿色:

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

现在看看这个:

 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 }); }); 

这是我想用这个规范testing的代码:

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

这在过去的几个月里已经让我陷入了数次的困境。 一个理论上的解决scheme是replacerequire()并返回一个用createSpy()创build的间谍。 但是require()是一个不可阻挡的野兽:它是每个源文件/模块中函数的不同“拷贝”。 在规范中对其进行stub不会取代“testee”源文件中的真正的require()函数。

另一种方法是在加载path中添加一些伪造的模块,但对我来说看起来太复杂了。

任何想法?

rewire对此很棒

 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); }); }); 

看起来我find了一个可接受的解决scheme。

规范帮手:

 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]; } }); 

规格:

 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 }); }); 

这并不完美,但做得很好。 它甚至没有混淆被testing者的源代码,这对我来说是一个标准。

这是非常有用的,但它不支持通过.andCallThrough()调用。

我能够适应它,所以我想我会分享:

 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; }; 

我今天需要这样做,并遇到这个职位。 我的解决scheme是:

在规范帮手:

 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 = {}; }); 

在规范中:

 AWS = stubModule('aws-sdk'); spyOn(AWS.S3, 'Client'); // do something expect(AWS.S3.Client).toHaveBeenCalled(); 

你可以轻轻地使用模块(https://github.com/felixge/node-gently)。 在示例中提到了劫持要求,而脏的NPM模块主动使用它,所以我想它是有效的。

还有另一种方法。 你可以把模块放在全局范围内,在需要的时候不要使用var

 someModule = require('someModule'); describe('whatever', function() { it('does something', function() { spyOn(global, 'someModule'); someFunctionThatShouldCallTheModule(); expect(someModule).toHaveBeenCalled(); } } 

您也可以将模块包装在另一个模块中:

 //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(); } } 

然后显然要确保无论在哪一个functionsomeFunctionThatShouldCallTheModule中,都需要包装器而不是真正的模块。