如何访问和testingnode.js模块中的内部(非导出)函数?

我试图找出如何testing内部(即不导出)的function在nodejs(最好与摩卡或茉莉花)。 而我不知道!

假设我有一个这样的模块:

function exported(i) { return notExported(i) + 1; } function notExported(i) { return i*2; } exports.exported = exported; 

而下面的testing(摩卡):

 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)); }); }); }); 

有没有办法unit testingnotExported函数,而不实际导出它,因为它不是要被暴露?

rewire模块绝对是答案。

这里是我的代码,用于访问未导出的函数并使用Mocha进行testing。

application.js中:

 function logMongoError(){ console.error('MongoDB Connection Error. Please make sure that MongoDB is running.'); } 

test.js:

 var rewire = require('rewire'); var chai = require('chai'); var should = chai.should(); var app = rewire('../application/application.js'); logError = app.__get__('logMongoError'); describe('Application module', function() { it('should output the correct error', function(done) { logError().should.equal('MongoDB Connection Error. Please make sure that MongoDB is running.'); done(); }); }); 

诀窍是设置NODE_ENV环境variables像test ,然后有条件地导出它。

假设你没有全局安装mocha,你可以在你的app目录的根目录下有一个Makefile,它包含以下内容:

 REPORTER = dot test: @NODE_ENV=test ./node_modules/.bin/mocha \ --recursive --reporter $(REPORTER) --ui bbd .PHONY: test 

这个make文件在运行mocha之前设置NODE_ENV。 然后你可以在命令行上用make test来运行你的摩卡testing。

现在,你可以有条件地导出你的函数,这个函数通常只在你的mochatesting运行时才导出:

 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; 

另一个答案build议使用虚拟机模块来评估文件,但这不起作用,并引发一个错误,说明出口没有定义。

编辑:

使用vm加载模块可能会导致意外的行为(例如, instanceof操作符不再适用于在此模块中创build的对象,因为全局原型与通常使用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;}; 

节点模块的全局module对象中还有一些东西可能也需要进入上面的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)); }); }); 

我发现了一个非常简单的方法,允许你在testing中testing,间谍和模拟这些内部函数:

假设我们有一个像这样的节点模块:

 mymodule.js: ------------ "use strict"; function myInternalFn() { } function myExportableFn() { myInternalFn(); } exports.myExportableFn = myExportableFn; 

如果我们现在想要testing嘲讽myInternalFn 而不是在生产中导出它,我们必须像这样改进文件:

 my_modified_module.js: ---------------------- "use strict"; var testable; // <-- this is new function myInternalFn() { } function myExportableFn() { testable.myInternalFn(); // <-- this has changed } exports.myExportableFn = myExportableFn; // the following part is new if( typeof jasmine !== "undefined" ) { testable = exports; } else { testable = {}; } testable.myInternalFn = myInternalFn; 

现在你可以testing,间谍和嘲笑myInternalFn无处不在你使用它作为myInternalFn和在生产中它不会被导出

你可以使用vm模块创build一个新的上下文,并在其中评估js文件,有点像repl。 那么你有权访问它声明的一切。

与Jasmine一起工作时,我试图更深入地根据Anthony Mayfield提出的基于rewire的解决scheme 。

我实现了以下function注意 :尚未经过彻底testing,只是作为一个可行的策略共享)

 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);