node.jsモジュールの内部(非エクスポート)関数にアクセスしてテストする方法は?


181

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関数を公開することを意図していないため、実際にエクスポートせずに関数を単体テストする方法はありますか?


1
たぶん、特定の環境でテストする関数を公開するだけでしょうか?ここでは標準的な手順がわかりません。
loganfsmyth 2013

回答:


243

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固有のエクスポートを使用して既存のすべてのモジュールを書き換える必要はありません。また、モジュールをテキストとして読み取る必要もありません。
Adam Yost 2015

美しいソリューション。さらに進んで、テストフレームワークのスパイと統合することは可能です。ジャスミンと協力して、私はこの戦略を試しました
Franco

2
素晴らしいソリューション。バベルタイプの人のための実用的なバージョンはありますか?
チャールズメリアム2017

2
jestおよびts-jest(typescript)でrewireを使用すると、次のエラーが発生しますCannot find module '../../package' from 'node.js'。これ見たことある?
2017年

2
Rewireにはjestとの互換性の問題があります。Jestは、カバレッジレポートでrewireから呼び出された関数を考慮しません。それは目的をいくらか破ります。
robross0606

10

秘訣は、NODE_ENV環境変数を次のように設定してtestから、条件付きでエクスポートすることです。

mochaをグローバルにインストールしていないと仮定すると、アプリディレクトリのルートに、次の内容を含むMakefileを含めることができます。

REPORTER = dot

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

.PHONY: test

このmakeファイルは、mochaを実行する前に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;

他の回答は、vmモジュールを使用してファイルを評価することを提案しましたが、これは機能せず、エクスポートが定義されていないことを示すエラーがスローされます。


8
これはハックのようですが、NODE_ENVブロックの場合、それを行わずに内部(エクスポートされていない)関数をテストする方法は本当にありませんか?
RyanHirsch 2014年

2
それはかなり厄介です。これは、この問題を解決するための最良の方法ではありません。
npiv 2014年

7

編集:

を使用してモジュールをロードすると、vm予期しない動作が発生する可能性があります(たとえばinstanceof、グローバルプロトタイプはで通常ロードされるモジュールで使用されるものとは異なるため、このようなモジュールで作成されたオブジェクトでは演算子は機能しなくなりますrequire)。以下の手法はもう使用せず、代わりにrewireモジュールを使用します。それは素晴らしく機能します。これが私の元の答えです:

スロッシュの答えについて詳しく説明しています...

少しハッキーな感じがしますが、アプリケーションモジュールで条件付きのエクスポートを行わなくても必要なことを実行できるようにする単純な「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;};

nodeモジュールの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));
  });
});

2
エクスポートされていない関数にアクセスする方法の例を教えてくださいrewire
Matthias

1
こんにちは、Matthias、私はあなたに私の答えでまさにそれをしている例をあなたに与えました。あなたがそれを気に入ったなら、たぶん私の質問にいくつか投票してください。:)私の質問のほとんどすべてが0になっており、StackOverflowは私の質問の凍結を考えています。X_X
アンソニー、

2

ジャスミンと協力して、私は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);

0

vmモジュールを使用して新しいコンテキストを作成し、その中のjsファイルを評価することができます。その後、宣言したものすべてにアクセスできます。


0

テスト内からこれらの内部関数をテスト、スパイ、モックすることができる非常に簡単な方法を見つけました。

次のようなノードモジュールがあるとします。

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


0

これは推奨される方法ではありませんrewireが、@ Antoineの提案どおりに使用できない場合は、いつでもファイルを読み取ってを使用できますeval()

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

レガシーシステムのクライアント側のJSファイルをユニットテストしているときに、これが便利であることがわかりました。

JSファイルはwindowrequire(...)and module.exportsステートメントなしで多くのグローバル変数を設定します(これらのステートメントを削除するために使用できるWebpackやBrowserifyなどのモジュールバンドルはありませんでした)。

これにより、コードベース全体をリファクタリングするのではなく、単体テストをクライアント側のJSに統合できました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.