我试图弄清楚如何在nodejs中测试内部(即不导出)函数(最好使用mocha或jasmine)。我也不知道!
假设我有一个这样的模块
function exported(i) {
return notExported(i) + 1;
}
function notExported(i) {
return i*2;
}
exports.exported = exported;
以及以下测试(摩卡):
var assert = require('assert'),
test = require('../modules/core/test');
describe('test', function(){
describe('#exported(i)', function(){
it('should return (i*2)+1 for any given i', function(){
assert.equal(3, test.exported(1));
assert.equal(5, test.exported(2));
});
});
});
是否有任何方法可以对notExported函数进行单元测试,而不实际导出它,因为它不打算公开?
Eval本身并不能工作(它只适用于顶级函数或var声明),你不能用Eval捕获用let或const声明的顶级变量到当前上下文中,但是,使用一个vm并在当前上下文中运行它将允许你在它执行后访问所有顶级变量…
eval("let local = 42;")
// local is undefined/undeclared here
const vm = require("vm")
vm.runInThisContext("let local = 42;");
// local is 42 here
...虽然"imported"模块中的声明或赋值可能在vm启动时与当前上下文中已经声明/定义的任何内容冲突,但前提是它们具有相同的名称。
这是一个一般的解决方案。这将为您导入的模块/单元添加少量不必要的代码,并且您的测试套件将不得不直接运行每个文件,以便以这种方式运行其单元测试。如果没有更多的代码,直接运行模块来做任何事情,而不是运行单元测试,这是不可能的。
在导入的模块中,检查文件是否是主模块,如果是,运行测试:
const local = {
doMath() {return 2 + 2}
};
const local2 = 42;
if (require.main === module) {
require("./test/tests-for-this-file.js")({local, local2});
}
然后在导入目标模块的测试文件/模块中:
module.exports = function(localsObject) {
// do tests with locals from target module
}
现在直接使用节点MODULEPATH运行目标模块以运行其测试。
与Jasmine合作,我试图深入研究Anthony Mayfield提出的基于重新布线的解决方案。
我实现了以下函数(注意:还没有完全测试,只是作为一个可能的策略分享):
function spyOnRewired() {
const SPY_OBJECT = "rewired"; // choose preferred name for holder object
var wiredModule = arguments[0];
var mockField = arguments[1];
wiredModule[SPY_OBJECT] = wiredModule[SPY_OBJECT] || {};
if (wiredModule[SPY_OBJECT][mockField]) // if it was already spied on...
// ...reset to the value reverted by jasmine
wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]);
else
wiredModule[SPY_OBJECT][mockField] = wiredModule.__get__(mockField);
if (arguments.length == 2) { // top level function
var returnedSpy = spyOn(wiredModule[SPY_OBJECT], mockField);
wiredModule.__set__(mockField, wiredModule[SPY_OBJECT][mockField]);
return returnedSpy;
} else if (arguments.length == 3) { // method
var wiredMethod = arguments[2];
return spyOn(wiredModule[SPY_OBJECT][mockField], wiredMethod);
}
}
使用这样的函数,你可以同时监视非导出对象和非导出顶级函数的方法,如下所示:
var dbLoader = require("rewire")("../lib/db-loader");
// Example: rewired module dbLoader
// It has non-exported, top level object 'fs' and function 'message'
spyOnRewired(dbLoader, "fs", "readFileSync").and.returnValue(FULL_POST_TEXT); // method
spyOnRewired(dbLoader, "message"); // top level function
然后你可以设定这样的期望:
expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled();
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION);
诀窍是将NODE_ENV环境变量设置为test之类的东西,然后有条件地导出它。
假设你没有全局安装mocha,你可以在你的应用目录的根目录下有一个Makefile,它包含以下内容:
REPORTER = dot
test:
@NODE_ENV=test ./node_modules/.bin/mocha \
--recursive --reporter $(REPORTER) --ui bbd
.PHONY: test
这个make文件在运行mocha之前设置NODE_ENV。然后可以在命令行中使用make test运行mocha测试。
现在,您可以有条件地导出您的函数,通常只有当您的mocha测试正在运行时才会导出:
function exported(i) {
return notExported(i) + 1;
}
function notExported(i) {
return i*2;
}
if (process.env.NODE_ENV === "test") {
exports.notExported = notExported;
}
exports.exported = exported;
另一个答案建议使用vm模块计算文件,但这不起作用,并抛出一个错误,指出exports未定义。
Eval本身并不能工作(它只适用于顶级函数或var声明),你不能用Eval捕获用let或const声明的顶级变量到当前上下文中,但是,使用一个vm并在当前上下文中运行它将允许你在它执行后访问所有顶级变量…
eval("let local = 42;")
// local is undefined/undeclared here
const vm = require("vm")
vm.runInThisContext("let local = 42;");
// local is 42 here
...虽然"imported"模块中的声明或赋值可能在vm启动时与当前上下文中已经声明/定义的任何内容冲突,但前提是它们具有相同的名称。
这是一个一般的解决方案。这将为您导入的模块/单元添加少量不必要的代码,并且您的测试套件将不得不直接运行每个文件,以便以这种方式运行其单元测试。如果没有更多的代码,直接运行模块来做任何事情,而不是运行单元测试,这是不可能的。
在导入的模块中,检查文件是否是主模块,如果是,运行测试:
const local = {
doMath() {return 2 + 2}
};
const local2 = 42;
if (require.main === module) {
require("./test/tests-for-this-file.js")({local, local2});
}
然后在导入目标模块的测试文件/模块中:
module.exports = function(localsObject) {
// do tests with locals from target module
}
现在直接使用节点MODULEPATH运行目标模块以运行其测试。