如何存根要求()/期望调用模块的“根”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
中,都需要包装器而不是真正的模块。