Sinon.JS

Kazuhito Hokamura

2012.05.22

社内勉強会

Sinon.JSってなんぞ

現在時刻が指定の期間内かチェックする関数

function isWithinDate(start, end) {
  var now = Date.now();
  var start = +new Date(start);
  var end = +new Date(end);
  return start < now && now < end;
}

// こんな感じで使う
isWithinDate('2012/05/10', '2012/05/30'); //=> true

ダメなテストコード

ok( isWithinDate('2012/05/10', '2012/05/30') );

今日は通るけど6月になったらこけちゃうよ!

sinon.useFakeTimers

// テストコード
var clock = sinon.useFakeTimers(+new Date('2012/05/13'));
ok( isWithinDate('2012/05/10', '2012/05/30') );
clock.restore();

現在時刻を強制変更

confirmを出して要素を削除する関数

function confirmAndRemove($elem) {
  if (window.confirm('ok?')) {
    $elem.remove();
  }
}

どうやってテストする?

ここ大事

重要なのは

  1. window.confirmが呼ばれた?
  2. window.confirmの引数は'ok?'だった?
  3. trueだったら$elem.removeが呼ばれた?
  4. falseだったら何もおきなかった?

ということ(下二つが特に重要)

sinon.stub

var $elem = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
stubConfirm.returns(true);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );

// 後始末
stubConfirm.restore();

sinon.stub

var $elem = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
stubConfirm.returns(true);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );

// 後始末
stubConfirm.restore();

sinon.stub

var $elem = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
stubConfirm.returns(true);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );

// 後始末
stubConfirm.restore();

sinon.spy

var $div = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
var spyRemove = sinon.spy($div, 'remove');
stubConfirm.returns(true);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );
ok( spyRemove.calledOnce );
ok( spyRemove.calledWith(undefined) );

// 後始末
stubConfirm.restore();
spyRemove.restore();

sinon.spy

var $div = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
var spyRemove = sinon.spy($div, 'remove');
stubConfirm.returns(true);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );
ok( spyRemove.calledOnce );
ok( spyRemove.calledWith(undefined) );

// 後始末
stubConfirm.restore();
spyRemove.restore();

confirmでキャンセルされたケース

var $div = $('<div>');
var stubConfirm = sinon.stub(window, 'confirm');
var spyRemove = sinon.spy($div, 'remove');
stubConfirm.returns(false);

// テスト対象の関数呼び出し
confirmAndRemove($div);

// テスト
ok( stubConfirm.calledOnce );
ok( stubConfirm.calledWith('ok?') );
ok( !spyRemove.called );

// 後始末
stubConfirm.restore();
spyRemove.restore();

プロキシ的な関数

function fetch(url) {
  var d = $.Deferred();

  $.ajax({
    url: url,
    dataType: 'json'
  }).done(function(data) {
    if (data.error) {
      d.reject(data.error);
    }
    else {
      d.resolve(data.result);
    }
  }).fail(function() {
    d.reject('ajax error');
  });

  return d;
}

だいじなこと

つまりテストしたいのは

function fetch(url) {
  var d = $.Deferred();

  $.ajax({
    url: url,
    dataType: 'json'
  }).done(function(data) {
    if (data.error) {
      d.reject(data.error);
    }
    else {
      d.resolve(data.result);
    }
  }).fail(function() {
    d.reject('ajax error');
  });

  return d;
}

レスポンスが成功したケース

var stubAjax = sinon.stub(jQuery, 'ajax');
stubAjax.returns( $.Deferred().resolve({ result: 'ok' }) );

fetch('/api/list').done(function(result) {
  ok( stubAjax.calledOnce );
  ok( stubAjax.calledWith({ url: '/api/list', dataType: 'json' }) );
  ok( result === 'ok' );
}).fail(function() {
  ok( false );
}).always(function() {
  stubAjax.restore();
});

データがエラーだったケース

var stubAjax = sinon.stub(jQuery, 'ajax');
stubAjax.returns( $.Deferred().resolve({ error: 'err!' }) );

fetch('/api/list').done(function(result) {
  ok( false );
}).fail(function(error) {
  ok( stubAjax.calledOnce );
  ok( stubAjax.calledWith({ url: '/api/list', dataType: 'json' }) );
  ok( error === 'err!' );
}).always(function() {
  stubAjax.restore();
});

レスポンスが失敗したケース

var stubAjax = sinon.stub(jQuery, 'ajax');
stubAjax.returns( $.Deferred().reject() );

fetch('/api/list').done(function(result) {
  ok( false );
}).fail(function(error) {
  ok( stubAjax.calledOnce );
  ok( stubAjax.calledWith({ url: '/api/list', dataType: 'json' }) );
  ok( error === 'ajax error' );
}).always(function() {
  stubAjax.restore();
});

まとめ

Thanks.