我试图弄清楚如何在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函数进行单元测试,而不实际导出它,因为它不打算公开?
编辑:
使用vm加载模块可能会导致意外的行为(例如,instanceof操作符不再适用于在这样的模块中创建的对象,因为全局原型与通常使用require加载的模块中使用的对象不同)。我不再使用下面的技术,而是使用rewire模块。它工作得很好。这是我最初的回答:
详细阐述srosh的回答……
这感觉有点粗糙,但我写了一个简单的“test_utils.js”模块,它应该允许你做你想做的事情,而不用在你的应用程序模块中有条件导出:
var Script = require('vm').Script,
fs = require('fs'),
path = require('path'),
mod = require('module');
exports.expose = function(filePath) {
filePath = path.resolve(__dirname, filePath);
var src = fs.readFileSync(filePath, 'utf8');
var context = {
parent: module.parent, paths: module.paths,
console: console, exports: {}};
context.module = context;
context.require = function (file){
return mod.prototype.require.call(context, file);};
(new Script(src)).runInNewContext(context);
return context;};
还有一些更多的东西包含在一个节点模块的全局模块对象中,可能也需要进入上面的上下文对象,但这是我需要它工作的最小集。
这里有一个摩卡BDD的例子:
var util = require('./test_utils.js'),
assert = require('assert');
var appModule = util.expose('/path/to/module/modName.js');
describe('appModule', function(){
it('should test notExposed', function(){
assert.equal(6, appModule.notExported(3));
});
});
与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);
我一直在使用一种不同的方法,没有任何依赖:
有一个包含我想测试的所有本地函数的__testing导出,该值取决于NODE_ENV,因此它只在测试中可访问:
// file.ts
const localFunction = () => console.log('do something');
const localFunciton2 = () => console.log('do something else');
export const exportedFunction = () => {
localFunction();
localFunciton2();
}
export const __testing = (process.env.NODE_ENV === 'test') ? {
localFunction, localFunction2
} : void 0;
// file.test.ts
import { __testing, exportedFunction } from './file,ts'
const { localFunction, localFunction2 } = __testing!;
// Now you can test local functions
这是不推荐的实践,但如果您不能像@Antoine建议的那样使用rewire,则始终可以读取文件并使用eval()。
var fs = require('fs');
const JsFileString = fs.readFileSync(fileAbsolutePath, 'utf-8');
eval(JsFileString);
在为遗留系统进行客户端JS文件单元测试时,我发现这很有用。
JS文件会在window下设置很多全局变量,没有任何require(…)和模块。exports语句(没有像Webpack或Browserify这样的模块绑定器可以删除这些语句)。
这使得我们可以在客户端JS中集成单元测试,而不是重构整个代码库。