これは私の問題の核心を示す些細な例です:
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
参照があります。
いまなら可能です!
proxyquire を公開しました。これは、テスト中にモジュール内のグローバルなrequireをオーバーライドします。
これは、必要なモジュールにモックを挿入するために、コードを変更する必要がないことを意味します。
Proxyquireには、テストしようとしているモジュールを解決し、1つの簡単なステップで必要なモジュールのモック/スタブを渡すことができる非常に単純なAPIがあります。
@Raynosは、伝統的にそれを達成するため、または代わりにボトムアップ開発を行うために、あまり理想的ではないソリューションに頼らなければならなかったことは正しい
これが私がproxyquireを作成した主な理由です-面倒なことなくトップダウンのテスト駆動開発を可能にします。
それがあなたのニーズに合うかどうかを判断するために、ドキュメントと例を見てください。
この場合のより良いオプションは、返されるモジュールのメソッドを模擬することです。
良くも悪くも、ほとんどの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モックを使用することです。 そのページ のフォローアップ
mock-require を使用します。テストするモジュールをrequire
する前に、必ずモックを定義してください。
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
メソッドを簡単にスタブしてコードをテストできるようになりました。コードはより冗長になりますが、テストも簡単になります。
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はかなり素晴らしいです、あなたはそれを使用する必要があります。必要なオーバーライドをテストのみにローカライズしたままにしておくことを強くお勧めします。
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 を参照してください。たとえば、ここではモジュールの「オートモック」を生成します。
それが役立つことを願っています;)
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'
できません。最下位のモジュールが最初にテストされ、モジュールを必要とする上位レベルのモジュールが後でテストされるように、ユニットテストスイートを構築する必要があります。
また、サードパーティのコードと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に公開され、どのコードもモジュラースコープに危険にさらされることに注意してください。