mocha で行うユニットテスト内でスパイ・モック化するなら「sinon」
以前、mocha という npm パッケージを使った単体テスト環境を構築したが、この mocha はテストランナーとしての側面が強く、特定のメソッドをモック化したりする機能は有していない。
Jasmine のように、spyOn().and.callFake()
や toHaveBeenCalled()
といったことをやりたく、似たようなライブラリがないか調べたところ、sinon というパッケージを使う例が多く見つかった。
目次
インストール
インストールはいつもどおり。テストランナーである mocha と、結果値検証に使う expect も入れてみる。
$ npm install --save-dev mocha expect sinon
指定の関数が呼ばれたか確認する
まずは、ある関数が呼ばれたかを確認するための、sinon.spy()
と .called
を使ってみる。Jasmine の toHaveBeenCalled()
相当だ。
テスト対象のコードは以下のとおり。
hoge.js
/** テスト対象のクラス */
class HogeClass {
/** テスト対象の関数 */
execHoge(value, callback) {
console.log(value);
callback();
}
}
コレに対するテストコードは以下のとおり。
hoge.spec.js
const expect = require('expect');
const sinon = require('sinon');
const HogeClass = require('../src/hoge');
describe('execHoge() メソッドのテスト', () => {
it('第2引数のコールバック関数が呼ばれること', () => {
// 第2引数に指定するコールバック関数をスパイで作る
const stubCallback = sinon.spy();
// テスト対象関数を実行する
const hogeClass = new HogeClass();
hogeClass.execHoge('Test', stubCallback);
// スパイ関数が実行されたかどうかを確認する
expect(stubCallback.called).toBe(true);
});
});
こんな感じ。
ある関数をモック化し、テスト用の戻り値を返す
Jasmine における and.callFake()
や and.returnValue()
相当。
- テスト対象コード
class HogeClass {
// FugaClass を DI (依存性注入) するような実装
constructor(fugaClass) {
this.fugaClass = fugaClass;
}
// FugaClass の exec() を実行し、その戻り値を2倍する関数
execFuga(value) {
const result = this.fugaClass.exec(value);
return result * 2;
}
}
ココで、DI する FugaClass
がどのように実装されているかは、単体テストにおいては関係ない。
- テストコード
const expect = require('expect');
const sinon = require('sinon');
const HogeClass = require('../src/hoge');
describe('execFuga() メソッドのテスト', () => {
// FugaClass.exec() メソッドのモックを控える変数
let stubFugaExec = null;
it('計算結果を2倍して返すこと', () => {
// とりあえずインスタンスを生成する
const dummyFugaClass = {
exec: () => { /* ダミー */ }
};
const hogeClass = new HogeClass(dummyFugaClass);
// FugaClass.exec() メソッドの動作をモック化する
stubFugaExec = sinon.stub(hogeClass.fugaClass, 'exec').callsFake((value) => {
// 引数が 10 であることを確認する
expect(value).toBe(10);
// 戻り値を固定で渡す
return 1;
});
// もしくは sinon.stub().returns(1);
// テスト対象関数を実行する (引数はデタラメでもいい)
const result = hogeClass.execFuga(10);
// 結果が「1」を2倍にした値であること
expect(result).toBe(2);
});
afterEach(() => {
// スタブで変更した関数を元に戻す
if(stubFugaExec && stubFugaExec.restore) {
stubFugaExec.restore();
}
});
});
こんな感じで一応テストはできる。
テスト対象クラスがテキトーすぎてイマイチ分かりづらいかもだけど、Jasmine における spyOn()
と同等の機能が sinon.stub()
である。.and.callFake()
や .and.returnValue()
といった繋ぎ方ではなく、.callsFake()
・.returns()
という風に書くので、対応するメソッド名を確認されたし。
そして Jasmine の spyOn()
と大きく違うのは、モック化した関数が1つのテストケース (it()
) 内でリセットされず、残り続けてしまうという挙動だ。そのために、sinon.stub()
の戻り値である SinonStub
クラスのインスタンスを変数に控えておき、1つのテストケースが終了した段階で .restore()
メソッドを呼んで元の関数に戻してあげている。コレを怠って、再度同じ関数に sinon.stub()
を適用しようとすると、例外が発生するので注意。
以上
sinon における Spy と Stub の違いがちょっと怪しい…。一応こんな感じでテストはできたものの、もっと効率的な書き方もありそうだ。