web-dev-qa-db-ja.com

他のモジュールを必要とするNode.jsモジュールを単体テストする方法、およびグローバルrequire関数をモックする方法は?

これは私の問題の核心を示す些細な例です:

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;

このコードの単体テストを作成しようとしています。 innerLib関数を完全にモックアウトせずに、requireの要件をモックアウトするにはどうすればよいですか?

だから、これはグローバルなrequireをモックアウトしようとしていて、それをしてもうまくいかないことを発見しています:

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};

問題は、underTest.jsファイル内のrequire関数が実際にはモックアウトされていないことです。まだグローバルなrequire関数を指しています。だから、モックインしている同じファイル内のrequire関数のみをモックアウトできるようです。ローカルのコピーをオーバーライドした後でも、グローバルなrequireを使用して何かを含めると、必要なファイルはまだグローバルなrequire参照があります。

147
Matthew Taylor

いまなら可能です!

proxyquire を公開しました。これは、テスト中にモジュール内のグローバルなrequireをオーバーライドします。

これは、必要なモジュールにモックを挿入するために、コードを変更する必要がないことを意味します。

Proxyquireには、テストしようとしているモジュールを解決し、1つの簡単なステップで必要なモジュールのモック/スタブを渡すことができる非常に単純なAPIがあります。

@Raynosは、伝統的にそれを達成するため、または代わりにボトムアップ開発を行うために、あまり理想的ではないソリューションに頼らなければならなかったことは正しい

これが私がproxyquireを作成した主な理由です-面倒なことなくトップダウンのテスト駆動開発を可能にします。

それがあなたのニーズに合うかどうかを判断するために、ドキュメントと例を見てください。

169
Thorsten Lorenz

この場合のより良いオプションは、返されるモジュールのメソッドを模擬することです。

良くも悪くも、ほとんどのnode.jsモジュールはシングルトンです。同じモジュールを必要とする2つのコードは、そのモジュールへの同じ参照を取得します。

これを活用し、 sinon のようなものを使用して、必要なアイテムをモックアウトできます。 mocha テストが続きます:

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});

Sinonには、アサーションを作成するのに優れた chaiとの統合 があり、 sinonをmochaと統合する にモジュールを作成して、スパイ/スタブのクリーンアップを容易にしました(テスト汚染を回避するため)。

UnderTestは関数のみを返すため、同じ方法でunderTestをモックすることはできません。

別のオプションは、Jestモックを使用することです。 そのページ のフォローアップ

108
Elliot Foster

mock-require を使用します。テストするモジュールをrequireする前に、必ずモックを定義してください。

11
Kunal

requireをあざけることは、私にとって厄介なハックのように感じます。私は個人的にそれを避け、コードをリファクタリングして、テストしやすくします。依存関係を処理するにはさまざまなアプローチがあります。

1)依存関係を引数として渡す

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}

これにより、コードが普遍的にテスト可能になります。欠点は、依存関係を渡す必要があるため、コードがより複雑に見える可能性があることです。

2)モジュールをクラスとして実装し、クラスのメソッド/プロパティを使用して依存関係を取得します

(これは、クラスの使用が合理的ではない不自然な例ですが、アイデアを伝えます)(ES6の例)

const innerLib = require('./path/to/innerLib')

class underTestClass {
    getInnerLib () {
        return innerLib
    }

    underTestMethod () {
        return this.getInnerLib().doComplexStuff()
    }
}

getInnerLibメソッドを簡単にスタブしてコードをテストできるようになりました。コードはより冗長になりますが、テストも簡単になります。

2
AlexM

好奇心の強い人のためにモジュールをモックするシンプルなコード

require.cacheを操作する部分と、require.resolveメソッドに注意してください。これは秘密のソースです。

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}

次のように使用

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})

しかし... proxyquireはかなり素晴らしいです、あなたはそれを使用する必要があります。必要なオーバーライドをテストのみにローカライズしたままにしておくことを強くお勧めします。

1
Jason Sebring

Jestを使用したことがあるなら、おそらくjestのモック機能に精通しているでしょう。

「jest.mock(...)」を使用すると、コード内のrequire-statementで発生する文字列を簡単に指定でき、その文字列を使用してモジュールが必要な場合は、代わりにモックオブジェクトが返されます。

例えば

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

「firebase-admin」のすべてのインポート/要件を、その「factory」機能から返されたオブジェクトに完全に置き換えます。

Jestは、実行するすべてのモジュールの周りにランタイムを作成し、モジュールにrequireの「フック」バージョンを挿入するため、jestを使用するときにそれを行うことができますが、jestなしではこれを行うことができません。

mock-require でこれを達成しようとしましたが、私にとっては、ソースのネストされたレベルでは機能しませんでした。 githubの次の問題をご覧ください: mock-requireは必ずしもMochaで呼び出されるとは限りません

これに対処するために、私はあなたが望むものを達成するために使用できる2つのnpm-modulesを作成しました。

1つのbabel-pluginとモジュールモッカーが必要です。

.babelrcで、次のオプションでbabel-plugin-mock-requireプラグインを使用します。

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

テストファイルでは、次のようにjestlike-mockモジュールを使用します。

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

jestlike-mockモジュールはまだ非常に初歩的なものであり、ドキュメントはあまりありませんが、コードもあまりありません。より完全な機能セットのPRに感謝します。目標は、「jest.mock」機能全体を再作成することです。

Jestがどのように実装しているかを確認するために、「jest-runtime」パッケージのコードを検索できます。 https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734 を参照してください。たとえば、ここではモジュールの「オートモック」を生成します。

それが役立つことを願っています;)

1
allesklarbeidir

mockery libraryを使用できます:

describe 'UnderTest', ->
  before ->
    mockery.enable( warnOnUnregistered: false )
    mockery.registerMock('./path/to/innerLib', { doComplexStuff: -> 'Complex result' })
    @underTest = require('./path/to/underTest')

  it 'should compute complex value', ->
    expect(@underTest()).to.eq 'Complex result'
1
Hirurg103

できません。最下位のモジュールが最初にテストされ、モジュールを必要とする上位レベルのモジュールが後でテストされるように、ユニットテストスイートを構築する必要があります。

また、サードパーティのコードとnode.js自体が十分にテストされていると想定する必要があります。

global.requireを上書きするモックフレームワークが近日中に届くと思います

モックを本当に注入する必要がある場合は、コードを変更してモジュラースコープを公開できます。

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}

これにより.__moduleがAPIに公開され、どのコードもモジュラースコープに危険にさらされることに注意してください。

1
Raynos