考虑以下茉莉规范:
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()是一只不可阻挡的野兽:它是每个源文件/模块中函数的不同"副本"。在规范中存根并不能取代"teste "源文件中真正的require()函数。
另一种选择是在加载路径中添加一些假模块,但对我来说这看起来太复杂了。
任何想法?
rewire 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);
});
});
看起来我找到了一个可以接受的解决方案。
规范帮助器:
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)。示例中提到了劫持require,并且dirty 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
在哪里,你都需要包装器而不是真正的模块。