❗️ 注意:この記事が作成されてから既に
日が経過しています。情報の鮮度にご注意ください
🖥 説明:この1週間、前任者のテストコードを修正していて、Sinonを使う中でいくつかの問題に遭遇したので、記録しておきます。
はじめに
以下ではSinonのspyインターフェースについてのみ説明しますが、stubなどのインターフェースにも同様に適用できます
テストではMocha Sinon Chaiライブラリ/フレームワークを使用していますが、ここでは紹介しません
单独でエクスポートされたモジュールをspyする
以下のようなfoo.jsモジュールがあり、foo関数をエクスポートしています
1 2 3
| export function foo { return 'foo'; }
|
以下のようなbar.jsモジュールがあり、このfoo関数をインポートしています
1 2 3 4
| import { foo } from './foo'; export function bar { return 'bar' + foo(); }
|
以下のようなテストケースbar.test.jsがあります
1 2 3 4 5 6 7 8 9
| import { bar } from './bar'; describe('ES6 导出模块测试-单独导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(bar); const result = bar();
expect(spy.called).to.equal.true; }); });
|
ここで失敗する理由は、bar.test.jsでインポートしたbarが関数を含む変数であり、Sinonはこの変数barをspyしているだけで、barに対応する関数をspyしていないからです。以下のテストケースも同じ状況です:
1 2 3 4 5 6 7 8 9
| import { bar as baz } from './bar'; describe('ES6 导出模块测试-单独导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(baz); const result = baz();
expect(spy.called).to.equal.true; }); });
|
この状況は次のセクションの内容で解決できます:
全部でエクスポートされたモジュールをspyする
1 2 3 4 5 6 7 8 9
| import * as allBar from './bar'; describe('ES6 导出模块测试-全部导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(allBar, 'bar'); const result = allBar.bar();
expect(spy.called).to.equal.true; }); });
|
では、エクスポートされていない関数をどのようにテストすればよいでしょうか?例えば最初のテストケースで、fooが呼び出されたかどうかをテストするにはどうすればよいでしょうか?
ここには2つの方法があります。1つ目は純粋なES6の方法で、インポートしたfoo関数をbar内で再度エクスポートするしかありません(これでは测试未导出函数の前提条件に違反してしまいます)。
2つ目の方法はbabelプラグインを使用する方法です。この方法の本質はES6をES5に変換してからテストすることです。プラグインの名前はbabel-plugin-rewireで、presetタイプのプラグインです。
rewireとは文字通り「再配線」という意味で、このプラグインは可以将某个模块中导入的但是并未导出却在该模块中调用的函数进行重新导出以方便测试少し説明が難しいので、完全版の例を見てみましょう:
foo.jsがあります:
1 2 3
| export function foo() { return 'foo'; }
|
foo.jsをインポートしているがfooをエクスポートしていないbar.jsがあります:
1 2 3 4 5
| import { foo } from './foo'; export default function bar() { return 'bar' + foo(); }
|
テストファイルbar.test.js:
1 2 3 4 5 6 7 8 9 10
| import bar from './bar'; describe('ES6 导出模块测试-默认导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(); bar.__Rewire__('foo', spy); const result = bar();
expect(spy.called).to.equal.true; }); });
|
コメントにあるように、デフォルトエクスポートは重要です。なぜなら、デフォルトエクスポート上の__Rewire__プロパティを通じてのみ再rewireが可能であり、以下のようにテストすることはできないからです:
1 2 3 4 5 6 7 8 9 10
| import { bar } from './bar'; describe('ES6 导出模块测试-默认导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(); bar.__Rewire__('foo', spy); const result = bar();
expect(spy.called).to.equal.true; }); });
|
前述の全部导出のような場合でも実現できません:
1 2 3 4 5 6 7 8 9 10 11 12
| import * as allBar from './bar'; describe('ES6 导出模块测试-默认导出', () => { it('应该能够 spy bar', () => { const spy = sinon.spy(); allBar.__Rewire__('foo', spy); const result = bar();
expect(spy.called).to.equal.true; }); });
|
もし全部导出を使用して非公開関数をテストしたい場合、テスト対象ファイルはその関数が根作用域という条件を満たす必要があります。例えば:
1 2 3 4 5
| import { foo } from './foo'; export foo; export function bar () { return 'bar' + foo(); }
|
この場合、テストファイルは次のように記述できます:
1 2 3 4
| import * as allBar from './bar';
const spy = sinon.spy(); allBar.__Rewire__('foo', spy);
|
終わりに
注意点として、これは関数テストだけでなく、デフォルトエクスポートが関数/クラスであるReactコンポーネントにも適用されます。なぜなら、それらのエクスポートの本質は同じく関数またはオブジェクトだからです。Reactコンポーネントをテストする際にはenzymeライブラリを使用するかもしれません。
- EOF -