mochaとnode.jsによるプライベート関数の単体テスト


131

node.js用に作成されたアプリケーションを単体テストするためにモカを使用しています

モジュールにエクスポートされていない関数を単体テストすることは可能ですか?

例:

私はこのように定義された多くの関数を持っています foobar.js

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

パブリックとしてエクスポートされたいくつかの関数:

exports.public_foobar3 = function(){
    ...
}

テストケースは次のように構成されています。

describe("private_foobar1", function() {
    it("should do stuff", function(done) {
        var stuff = foobar.private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

private_foobar1はエクスポートされないため、明らかにこれは機能しません。

プライベートメソッドを単体テストする正しい方法は何ですか?モカにはそれを行うためのいくつかの組み込みメソッドがありますか?


回答:


64

関数がモジュールによってエクスポートされない場合、モジュールの外部のテストコードから呼び出すことはできません。これはJavaScriptの仕組みによるものであり、Mocha自体ではこれを回避することはできません。

プライベート関数のテストが正しいことだと判断したいくつかの例では、モジュールがテスト設定で実行されているかどうかを確認するためにモジュールがチェックするいくつかの環境変数を設定しました。テストセットアップで実行する場合は、テスト中に呼び出すことができる追加の関数をエクスポートします。

ここでは「環境」という言葉を大まかに使用しています。これはprocess.env、「現在テスト中です」というモジュールと通信できるチェックなどを意味する場合があります。私がこれをしなければならなかった例はRequireJS環境であり、私はmodule.configこの目的のために使用しました。


2
条件付きで値をエクスポートすることは、ES6モジュールと互換性がないようです。取得していますSyntaxError: 'import' and 'export' may only appear at the top level
aij

1
はい@aij ES6の静的な輸出のためにあなたが使用することはできませんimportexportブロックの内部を。最終的には、システムローダーを使用してES6でこの種のことを実現できるようになります。これを回避する1つの方法はmodule.exports = process.env.NODE_ENV === 'production' ? require('prod.js') : require('dev.js')、es6コードの違いを使用して、それぞれのファイルに保存することです。
cchamberlain

2
あなたが完全なカバレッジを持っているなら、あなたがそれらを公開したかどうかにかかわらず、あなたはすべてのプライベート関数をテストしていると思います。
Ziggy 2017

1
@aijあなたは条件付きでエクスポートすることができます...この回答を参照してください。stackoverflow.com/questions/39583958/...
RayLoveless

187

再配線モジュールを確認してください。モジュール内のプライベート変数と関数を取得(および操​​作)できます。

したがって、あなたの場合、使用法は次のようになります:

var rewire = require('rewire'),
    foobar = rewire('./foobar'); // Bring your module in with rewire

describe("private_foobar1", function() {

    // Use the special '__get__' accessor to get your private function.
    var private_foobar1 = foobar.__get__('private_foobar1');

    it("should do stuff", function(done) {
        var stuff = private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

3
@Jaro私のコードのほとんどは、AMDモジュールの形式であり、再配線は処理できません(AMDモジュールは関数ですが、再配線は「関数内の変数」を処理できないため)。または、再配線が処理できない別のシナリオである、積み重ねられます。実際、rewireを検討する人は、使用する前に、制限事項(先にリンクされたもの)を最初に読んでおくとよいでしょう。私は、a)「プライベート」なものをエクスポートする必要があり、b)再配線の制限に遭遇しない単一のアプリを持っていません。
ルイ

1
ほんの少し、コードカバレッジはこのように書かれたテストを取得できない場合があります。少なくとも、これはJestの組み込みのカバレッジツールを使用して見たものです。
Mike Stead 2017

Rewireもjestの自動モッキングツールではうまく機能しません。私はまだ冗談の利点を活用し、いくつかのプライベート変数にアクセスする方法を探しています。
btburton42 2017

だから私はこの作品を作ってみましたが、私はtypescriptを使用していますが、これがこの問題の原因だと思います。基本的に、次のエラーが発生しますCannot find module '../../package' from 'node.js'。これを知っている人はいますか?
2017年

再配線は正常に動作している.tstypescript私が使用して実行ts-node @cluを
muthukumar selvaraj

24

GoogleのエンジニアであるPhilip Waltonがブログで説明している、プライベートメソッドをテストするための非常に優れたワークフローを次に示します

原理

  • 通常通りコードを書く
  • プライベートメソッドを別のコードブロックでオブジェクトにバインドし、_たとえば、
  • 開始コメントと終了コメントでコードブロックを囲む

次に、ビルドタスクまたは独自のビルドシステム(grunt-strip-codeなど)を使用して、本番ビルド用にこのブロックを削除します。

テストビルドはプライベートAPIにアクセスできますが、本番ビルドはアクセスできません。

スニペット

次のようにコードを記述します。

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

そして、そのようなあなたの不快なタスク

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

より深く

後の記事では、「プライベートメソッドのテスト」の「理由」について説明しています。


1
同様のワークフローをサポートできるように見えるwebkitプラグインも見つかりました: webpack-strip-block
JRulle

21

シンプルにしたい場合は、プライベートメンバーもエクスポートしますが、パブリックAPIから明確に分離します。たとえば、プレフィックスを付ける_か、単一のプライベートオブジェクトの下にネストします

var privateWorker = function() {
    return 1
}

var doSomething = function() {
    return privateWorker()
}

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}

7
これは、モジュール全体が実際に非公開であり、一般的な使用を目的としていない場合に実行しました。しかし、汎用モジュールでは、コードがテストされている場合にのみ、テストに必要なものを公開することを好みます。結局のところ、誰かがテスト環境を偽ってプライベートなものにアクセスすることを妨げるものは何もありませんが、自分のアプリケーションでデバッグをしているときは、必要のないシンボルは表示しないほうがいいですパブリックAPIの一部。このようにして、設計されていない目的のためにAPIを乱用する誘惑はすぐにはありません。
Louis

2
ネストされた構文を使用することもできます{... private:{worker:worker}}
Jason

2
モジュールがすべて純粋な関数である場合、これを行うことの欠点はありません。状態を維持および変更している場合は、注意してください
Ziggy

5

:私はあなたが有用見つけるかもしれないことを、この目的のためにNPMパッケージを作っ必要-から

基本的に、非公開メソッドは次の方法で公開します。

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}

注: もちろんtestExports、任意の有効な名前を使用できますexports

そして別のモジュールから:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;

1
この方法には実際的な利点はありません。「プライベート」シンボルをよりプライベートにすることはありません。(誰でもrequireFrom適切なパラメーターで呼び出すことができます。)また、モジュールtextExportsが読み込まれる前にrequire呼び出しによって読み込まれた場合は、が返されます。(私はそれをテストしました。)モジュールのロード順序を制御することはしばしば可能ですが、それが常に実用的であるとは限りません。(SOに関するいくつかのMochaの質問で証明されています。)このソリューションは、一般にAMDタイプのモジュールでも機能しません。(テストのために毎日AMDモジュールをノードにロードしています。) requireFromrequireFromundefined
Louis

AMDモジュールでは動作しないはずです。Node.jsはcommon.jsを使用しますが、AMDを使用するように変更した場合は、通常とは異なります。
jemiloii 2015

@JemiloII何百人もの開発者がNode.jsを毎日使用してAMDモジュールをテストしています。それを行うことには、「標準外」は何もありません。Node.jsにはAMDローダーは付属していませんが、Node.jsがローダーを拡張して開発者が開発したいフォーマットをロードする明示的なフックを提供していることを考えると、Node.jsにはAMDローダーが付属していません。
Louis

それは標準外です。amdローダーを手動で含める必要がある場合、それはnode.jsの標準ではありません。AMDがnode.jsコードで表示されることはほとんどありません。ブラウザでは表示されますが、ノードです。いいえ、私はそれが行われていないと言っているのではありません。私たちがコメントしている質問とこの答えだけで、amdモジュールについては何も言いません。したがって、amdローダーを使用していることを誰も述べずに、ノードのエクスポートはamdで動作するべきではありません。私は注意したいのですが、commonjsはes6のエクスポートで解決されるかもしれません。いつの日か、1つのエクスポート方法を使用できることを願っています。
jemiloii

4

Internal()という名前の関数を追加し、そこからすべてのプライベート関数を返します。次に、このInternal()関数がエクスポートされます。例:

function Internal () {
  return { Private_Function1, Private_Function2, Private_Function2}
}

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

次のような内部関数を呼び出すことができます。

let test = require('.....')
test.Internal().Private_Function1()

私はこのソリューションが一番好きです:

  • 一つだけの関数の内部は、()は常にエクスポートされます。このInternal()関数は、プライベート関数をテストするために常に使用されます。
  • 実装は簡単です
  • 量産コードへの影響が少ない(1つの追加機能のみ)

2

私は続い@barwin解答とユニットテストがで作ることができるか確認のReWireモジュール。このソリューションが機能することを確認できます。

モジュールは、パブリックとプライベートの2つの部分で必要です。パブリック関数の場合、標準的な方法でそれを行うことができます:

const { public_foobar3 } = require('./foobar');

プライベートスコープの場合:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

主題について詳しく知るために、モジュール全体のテストを行った実用的な例を作成しました。テストにはプライベートスコープとパブリックスコープが含まれます。

詳細については、主題を完全に説明している記事(https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f)を確認することをお勧めします。コードサンプルも含まれています。


2

これが必ずしもあなたが探している答えではないことは知っていますが、私が見つけたのは、ほとんどの場合、プライベート関数がテストする価値がある場合、それ自体のファイルにある価値があるということです。

たとえば、次のように、パブリックメソッドと同じファイルにプライベートメソッドを置くのではなく...

src / thing / PublicInterface.js


function helper1 (x) {
    return 2 * x;
}

function helper2 (x) {
    return 3 * x;
}

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

...次のように分割します。

src / thing / PublicInterface.js

import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

src / thing / internal / helper1.js

export function helper1 (x) {
    return 2 * x;
}

src / thing / internal / helper2.js

export function helper2 (x) {
    return 3 * x;
}

そうすれば、Rewireやその他の「マジック」を使用しなくても、簡単にテストhelper1helper2て現状のままでテストできます(これは、デバッグ中、またはTypeScriptに移行しようとするときに問題があることはわかっています)。新しい同僚のための理解可能性)。そして、それらがと呼ばれるサブフォルダinternalまたはそのような場所にあると、意図しない場所での偶発的な使用を回避するのに役立ちます。


PS:「プライベート」メソッドのもう1つの一般的な問題は、ヘルパーをテストpublicMethod1publicMethod2てモックしたい場合、通常、Rewireのようなものが必要です。ただし、それらが別々のファイルにある場合は、Proxyquireを使用して実行できます。これは、Rewireとは異なり、ビルドプロセスに変更を加える必要がなく、読み取りやデバッグが簡単で、TypeScriptでもうまく機能します。


1

プライベートメソッドをテストに使用できるようにするには、次のようにします。

const _myPrivateMethod: () => {};

const methods = {
    myPublicMethod1: () => {},
    myPublicMethod2: () => {},
}

if (process.env.NODE_ENV === 'test') {
    methods._myPrivateMethod = _myPrivateMethod;
}

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