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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14874208/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 17:10:04  来源:igfitidea点击:

How to access and test an internal (non-exports) function in a node.js module?

node.jsunit-testingjasminemocha

提问by xavier.seignard

I'm trying to figure out on how to test internal (i.e. not exported) functions in nodejs (preferably with mocha or jasmine). And i have no idea!

我试图弄清楚如何在 nodejs 中测试内部(即不导出)函数(最好使用 mocha 或 jasmine)。我不知道!

Let say I have a module like that:

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

function exported(i) {
   return notExported(i) + 1;
}

function notExported(i) {
   return i*2;
}

exports.exported = exported;

And the following test (mocha):

以及以下测试(摩卡咖啡):

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

Is there any way to unit test the notExportedfunction without actually exporting it since it's not meant to be exposed?

有没有办法在notExported不实际导出函数的情况下对函数进行单元测试,因为它不打算公开?

回答by Anthony

The rewiremodule is definitely the answer.

联控模块是绝对的答案。

Here's my code for accessing an unexported function and testing it using Mocha.

这是我用于访问未导出函数并使用 Mocha 对其进行测试的代码。

application.js:

应用程序.js:

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

test.js:

测试.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();
  });
});

回答by Matthew Bradley

The trick is to set the NODE_ENVenvironment variable to something like testand then conditionally export it.

诀窍是将NODE_ENV环境变量设置为类似的值test,然后有条件地导出它。

Assuming you've not globally installed mocha, you could have a Makefile in the root of your app directory that contains the following:

假设您没有全局安装 mocha,您可以在应用程序目录的根目录中有一个 Makefile,其中包含以下内容:

REPORTER = dot

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

.PHONY: test

This make file sets up the NODE_ENV before running mocha. You can then run your mocha tests with make testat the command line.

这个 make 文件在运行 mocha 之前设置了 NODE_ENV。然后你可以make test在命令行运行你的 mocha 测试。

Now, you can conditionally export your function that isn't usually exported only when your mocha tests are running:

现在,您可以有条件地导出通常仅在 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;

The other answer suggested using a vm module to evaluate the file, but this doesn't work and throws an error stating that exports is not defined.

另一个答案建议使用 vm 模块来评估文件,但这不起作用并引发错误,指出未定义导出。

回答by mhess

EDIT:

编辑:

Loading a module using vmcan cause unexpected behavior (e.g. the instanceofoperator no longer works with objects that are created in such a module because the global prototypes are different from those used in module loaded normally with require). I no longer use the below technique and instead use the rewiremodule. It works wonderfully. Here's my original answer:

使用 加载模块vm可能会导致意外行为(例如,instanceof操作符不再处理在此类模块中创建的对象,因为全局原型与正常加载的模块中使用的不同require)。我不再使用以下技术,而是使用rewire模块。它工作得很好。这是我的原始答案:

Elaborating on srosh's answer...

详细说明 srosh 的答案......

It feels a bit hacky, but I wrote a simple "test_utils.js" module that should allow you to do what you want without having conditional exports in your application modules:

感觉有点hacky,但我写了一个简单的“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;};

There are some more things that are included in a node module's gobal moduleobject that might also need to go into the contextobject above, but this is the minimum set that I need for it to work.

节点模块的全局module对象中还包含一些其他内容,这些内容可能也需要进入上述context对象中,但这是我使其工作所需的最小集合。

Here's an example using mocha BDD:

下面是一个使用 mocha 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));
  });
});

回答by Franco

Working with Jasmine, I tried to go deeper with the solution proposed by Anthony Mayfield, based on rewire.

与 Jasmine 合作,我尝试更深入地研究Anthony Mayfield 提出的基于rewire解决方案

I implemented the following function (Caution: not yet thoroughly tested, just shared as a possibile strategy):

我实现了以下功能注意:尚未彻底测试,只是作为一种可能的策略共享)

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

With a function like this you could spy on both methods of non-exported objects and non-exported top level functions, as follows:

使用这样的函数,您可以监视非导出对象和非导出顶级函数的两种方法,如下所示:

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

Then you can set expectations like these:

然后你可以设置这样的期望:

expect(dbLoader.rewired.fs.readFileSync).toHaveBeenCalled();
expect(dbLoader.rewired.message).toHaveBeenCalledWith(POST_DESCRIPTION);

回答by heinob

I have found a quite simple way that allows you to test, spy and mock those internalfunctions from within the tests:

我找到了一种非常简单的方法,可以让您在测试中测试、监视和模拟这些内部函数:

Let's say we have a node module like this:

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

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

function myInternalFn() {

}

function myExportableFn() {
    myInternalFn();   
}

exports.myExportableFn = myExportableFn;

If we now want to test andspy andmock myInternalFnwhile not exporting it in productionwe have to improve the file like this:

如果我们现在想要测试监视模拟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;

Now you can test, spy and mock myInternalFneverywhere where you use it as testable.myInternalFnand in production it is not exported.

现在,您可以myInternalFn在任何使用它的地方进行测试、监视和模拟,testable.myInternalFn并且在生产中它不会被导出

回答by srosh

you can make a new context using vmmodule and eval the js file in it, sort of like repl does. then you have access to everything it declares.

你可以使用vm模块创建一个新的上下文并评估其中的 js 文件,有点像 repl 那样。那么您就可以访问它声明的所有内容。

回答by Abhishek Divekar

This is not recommended practice, but if you can't use rewireas suggested by @Antoine, you can always just read the file and use eval().

这不是推荐的做法,但如果您不能rewire按照@Antoine 的建议使用,您可以随时读取文件并使用eval().

var fs = require('fs');
const JsFileString = fs.readFileSync(fileAbsolutePath, 'utf-8');
eval(JsFileString);

I found this useful while unit testing client-side JS files for a legacy system.

我发现这在对遗留系统的客户端 JS 文件进行单元测试时很有用。

The JS files would set up a lot of global variables under windowwithout any require(...)and module.exportsstatements (there was no module bundler like Webpack or Browserify available to remove these statements anyway).

JS 文件会在window没有任何require(...)andmodule.exports语句的情况下设置许多全局变量(无论如何,没有像 Webpack 或 Browserify 这样的模块捆绑器可用于删除这些语句)。

Rather than refactor the entire codebase, this allowed us to integrate unit tests in our client-side JS.

这不是重构整个代码库,而是允许我们在客户端 JS 中集成单元测试。