☕️

mocha + Sinon.JS テストダブル集

2021/07/04に公開

mochaSinon.JSを用いたテストダブルの準備について、自分用メモ。TypeScriptで書いているため、クラスやコンストラクタといった用語を平気で使います。

#基本形
例えばTodoというクラスについて一部をスタブ化したいとき。

spec.ts
describe('Todo', () => {
  var Todo: any = require('../lib/todo'); // 既に大部分が実装されているものとする
  
  var TodoStub: any;
  beforeEach(() => {
    /* TodoStub */
    TodoStub = {
      get:    sinon.stub(Todo.prototype, 'get').returns('dummy'),
      put:    sinon.stub(Todo.prototype, 'put'),
      remove: sinon.stub(Todo.prototype, 'remove'),
    };
  });
});

検証は次のように。

assert(TodoStub.get.callCount === 1);
assert(TodoStub.put.getCall(0).args[0] === 'foo'); // 1回目に呼ばれたput()の1個目の引数

よその解説ではしばしばvar spy = sinon.spy(...としているが、こうすると場合によって変数がやたらと増えるため、オブジェクトにまとめておくほうが扱いやすいと判断しました。

モックを用意してからのスタブ化

テストに必要なクラスがそもそも現存しておらずモックの準備が必要な場合。ここでいうモックはSinon.JS API上でいうmockとは異なります。

spec.ts
describe('Storage', () => {
  var StorageStub: any;
  beforeEach(() => {
    /* StorageStub */
    var StorageCtor: any;
    StorageCtor = () => {};                   // コンストラクタのモック
    StorageCtor.newStorage = () => {};        // クラスメソッドのモック
    StorageCtor.prototype.getItem = () => {}; // インスタンスメソッドのモック
    StorageCtor.prototype.setItem = () => {};
    
    StorageStub = { // それぞれをstub化
      newStorage: sinon.stub(StorageCtor,           'newStorage').returns(StorageCtor.prototype)
      getItem:    sinon.stub(StorageCtor.prototype, 'getItem').returns('dummy'),
      setItem:    sinon.stub(StorageCtor.prototype, 'setItem')
    };
  });
});

TypeScriptanyを多用していますが、アロー関数式が使いたいだけで型チェックは求めていないためです。

new演算子の引数検証

spec.ts
describe('Storage', () => {
  var StorageStub: any;
  beforeEach(() => {
    /* StorageStub */
    var StorageCtor: any;
    StorageCtor = function() {
      this.checkArgs(arguments);
    };
    StorageCtor.prototype.checkArgs = () => {};
    
    StorageStub = {
      checkArgs: sinon.stub(StorageCtor.prototype, 'checkArgs')
    };
  });
});

検証は次のように。

assert(StorageStub.checkArgs.getCall(0).args[0][0] === 'foo'); // 1回目に呼ばれたnew Storage()の1個目の引数

argumentsで可変引数をまとめて渡しているので、2個目の引数を検証したい場合.args[1]ではなく.args[0][1]とします。

#スタブの全リセット
beforeEach()で走った処理でスタブのカウントが増えてしまったりがあるので、最後に一度リセット。

spec.ts
/* reset all */
_.forEach([
  TodoStub,
  StorageStub,
  OtherStub
], (stubGroup: any) => {
  _.forEach(stubGroup, (stub: any) => {
    stub.reset();
  });
});

当たり前のようにlodash使ってますが、この辺は好みで。mochaのafterEach内ではrestore()することも忘れずに。


この辺をスニペットに登録してサクサク使ってます。もっと簡単な方法があったら教えてください。それでは。

Discussion