Как заглушить require () / expect вызовы функции "root" модуля?
рассмотрим следующие спецификации Жасмин:
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
});
});
это код, который я хотел бы проверить с помощью этой спецификации:
something.act = function() { require('my_module')(); };
за последние несколько месяцев я несколько раз увязал в этом. Одним из теоретических решений было бы заменить require () и вернуть шпиона, созданного с помощью createSpy (). Но require() является непреодолимым зверь: это другая "копия" функции в каждом исходном файле / модуле. Тушение его в спецификации не заменит функцию real require() в исходном файле "testee".
альтернативой является добавление некоторых поддельных модулей в путь загрузки, но это выглядит слишком сложным для меня.
есть идеи?
6 ответов
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);
});
});
похоже, я нашел приемлемое решение.
помощник спецификации:
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
});
});
это не идеально, но делает работу очень хорошо. Он даже не связывается с пол исходный код, который является своего рода критерий для меня.
мне нужно было сделать это сегодня и наткнулся на этот пост. Мое решение таково:
в помощнике спецификации:
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();
Это было очень полезно, но он не поддерживает вызов через .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;
};
вы можете использовать нежно модуль (https://github.com/felixge/node-gently). Hijacking require упоминается в примерах, и грязный модуль 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();
}
}
а затем, очевидно, убедитесь, что где someFunctionThatShouldCallTheModule
is, вам требуется обертка, а не реальный модуль.