[node.js] node.js 모듈에서 내부 (비 수출) 기능에 액세스하고 테스트하는 방법은 무엇입니까?

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노출되지 않기 때문에 실제로 내 보내지 않고 단위 테스트를 수행 할 수있는 방법이 있습니까?



답변

와이어 모듈이 정답입니다.

내 보낸 함수에 액세스하고 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();
  });
});


답변

비결은 NODE_ENV환경 변수를 비슷한 것으로 설정 test한 다음 조건부로 내보내는 것입니다.

전체적으로 mocha를 설치하지 않았다고 가정하면 앱 디렉토리의 루트에 다음을 포함하는 Makefile이있을 수 있습니다.

REPORTER = dot

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

.PHONY: test

이 파일은 mocha를 실행하기 전에 NODE_ENV를 설정합니다. 그런 다음 make test명령 행에서 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;

다른 대답은 파일을 평가하기 위해 vm 모듈을 사용하도록 제안했지만 작동하지 않으며 내보내기가 정의되지 않았다는 오류가 발생합니다.


답변

편집하다:

를 사용하여 모듈을로드 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위 의 객체 로 가야 할 것이 더 있지만, 이것이 작동하는 데 필요한 최소 세트입니다.

다음은 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));
  });
});


답변

Jasmine과 함께 rewire를 기반으로 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);


답변

vm 모듈을 사용하여 새로운 컨텍스트를 만들고 repl처럼 js 파일을 평가할 수 있습니다. 그런 다음 선언 한 모든 내용에 액세스 할 수 있습니다.


답변

테스트 내에서 내부 기능 을 테스트, 스파이 및 조롱 할 수있는 매우 간단한 방법을 찾았습니다 .

다음과 같은 노드 모듈이 있다고 가정 해 봅시다.

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 프로덕션 환경에서 내 보내지 않은 곳 어디에서나 테스트, 스파이 및 조롱 할 수 있습니다 .


답변

권장되는 방법은 아니지만 rewire@Antoine에서 제안한대로 사용할 수없는 경우 항상 파일을 읽고 사용할 수 있습니다 eval().

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

레거시 시스템에 대한 클라이언트 측 JS 파일을 단위로 테스트하는 동안이 기능이 유용하다는 것을 알았습니다.

JS 파일은 and 문 window없이 많은 전역 변수를 설정합니다 (어쨌든 이러한 문을 제거하는 데 사용할 Webpack 또는 Browserify와 같은 모듈 번 들러는 없었습니다).require(...)module.exports

전체 코드베이스를 리팩토링하는 대신 클라이언트 측 JS에서 단위 테스트를 통합 할 수있었습니다.