2013-02-14 77 views
80

我想弄清楚如何在nodejs中測試內部(即不導出)函數(最好是用摩卡或茉莉花)。我不知道!如何訪問和測試node.js模塊中的內部(非導出)函數?

讓說我有一個這樣的模塊:

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功能它不是要被暴露?

+1

也許只是暴露功能測試時,在特定的環境?我不知道這裏的標準程序。 – loganfsmyth 2013-02-14 17:01:31

回答

121

rewire模塊絕對是答案。

這是我的代碼,用於訪問未導出的函數並使用Mocha對其進行測試。

的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(); 
    }); 
}); 
+2

這應該絕對是最好的答案。它不需要使用NODE_ENV特定的導出來重寫所有現有的模塊,也不需要將模塊作爲文本讀入。 – 2015-07-22 16:00:48

+0

美麗的解決方案。有可能在測試框架中進一步將其與間諜集成。與Jasmine一起工作,我嘗試了[這種策略](http://stackoverflow.com/a/39818726/659788)。 – Franco 2016-10-02 17:08:03

+0

感謝您的驚人的解決方案...你讓我的一天:) – Sohail 2017-03-07 22:36:27

0

你可以使用vm模塊創建一個新的上下文,並對其中的js文件進行評估,有點像repl。那麼你可以訪問它聲明的所有內容。

11

訣竅是將NODE_ENV環境變量設置爲像test之類的東西,然後有條件地將其導出。

假設你已經不是全局安裝摩卡,你可以有一個Makefile在你的應用程序目錄,其中包含以下根:

REPORTER = dot 

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

.PHONY: test 

此make文件運行前摩卡設立的NODE_ENV。然後你可以在命令行上用make test運行你的摩卡測試。

現在,您可以有條件地導出函數,並不通常只在出口時的摩卡測試運行:使用虛擬測量模塊,以評估該文件

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; 

對方回答的建議,但是這並未」 t工作並拋出一個錯誤,指出沒有定義輸出。

+5

這似乎是一個黑客,是否真的沒有辦法測試內部(非導出)的功能,而不做這個,如果NODE_ENV塊? – RyanHirsch 2014-01-15 16:47:29

+2

這很骯髒。這不是解決這個問題的最好方法。 – npiv 2014-10-23 20:45:49

5

編輯:

加載使用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;}; 

有一些包含在一個節點模塊的gobal 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)); 
    }); 
}); 
+2

你能舉一個例子,你如何使用'rewire'訪問一個非導出函數? – Matthias 2015-06-09 20:49:31

+1

嘿馬提亞斯,我已經給你做了一個例子,在我的答案中完全一樣。如果你喜歡它,也許upvote我的幾個問題? :)幾乎所有的問題都在0,而StackOverflow正在考慮凍結我的問題。 X_X – Antoine 2015-06-12 02:04:01

1

我發現了一個很簡單的方法,可以讓你測試,間諜,並從測試中嘲笑那些內部功能:

比方說,我們有一個節點模塊是這樣的:

mymodule.js: 
------------ 
"use strict"; 

function myInternalFn() { 

} 

function myExportableFn() { 
    myInternalFn(); 
} 

exports.myExportableFn = myExportableFn; 

如果我們現在要測試間諜模擬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; 

現在可以測試,間諜和模擬myInternalFn無處不在,你使用它作爲testable.myInternalFn和生產它是未出口

1

茉莉工作,我試圖去深入與solution proposed by Anthony Mayfield,基於rewire

我實現了以下功能注意:尚未徹底測試,就如同一個不可能性的戰略共享)

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); 
相關問題