如何访问和Node.js的模块测试内部(非出口)的功能?(How to access and tes

2019-07-19 21:14发布

我试图找出如何在测试的NodeJS内(即不输出)功能(最好用摩卡或茉莉)。 而且我不知道!

让说我有一个这样的模块:

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而不实际出口它,因为它注定不会被曝光功能?

Answer 1:

该联控模块是绝对的答案。

这是我访问一个未导出的功能和使用摩卡测试它的代码。

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


Answer 2:

关键是要设定NODE_ENV环境变量类似test ,然后有条件地导出。

假设你已经不是全局安装摩卡,你可以有一个Makefile在你的应用程序目录,其中包含以下根:

REPORTER = dot

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

.PHONY: test

本作文件设置运行摩卡前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;

对方的回答使用提出了虚拟测量模块,以评估该文件,但是,这并不工作,并抛出一个错误,说明出口是没有定义。



Answer 3:

编辑:

通过加载一个模块vm可能会导致意外的行为(例如instanceof运算符不再被这样的模块中创建的对象的作品,因为全球的原型是来自那些模块使用的不同与正常加载require )。 我不再使用下面的技术,而是使用联控模块。 它奇妙的作品。 下面是我原来的答复:

在阐述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));
  });
});


Answer 4:

我发现了一个很简单的方法,可以让你测试,间谍,并从测试中嘲笑那些内部功能:

比方说,我们有一个节点模块是这样的:

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并在生产中它不出口



Answer 5:

茉莉工作,我试图去更深的安东尼·梅菲尔德提出的解决方案的基础上, 再布线 。

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

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


Answer 6:

您可以通过创建一个新的上下文VM模块和EVAL JS文件在里面,有点像REPL一样。 然后您可以访问它声明的一切。



Answer 7:

这是不推荐的做法,但如果你不能使用rewire被@Antoine的建议,你永远可以读取该文件,并使用eval()

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

我发现这非常有用,而单元测试客户端的JS文件遗留系统。

该JS文件就设立了很多下的全局变量的window ,没有任何require(...)module.exports语句(没有模块捆绑喜欢的WebPack或Browserify可有效地消除这些语句反正)。

而不是重构整个代码库,这使我们能够在我们的客户端JS单元测试集成。



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