テストしづらい部品をダミーに
置き換えてテストしやすくする
jQuery#triggerのテスト
describe('jQuery#trigger', function() {
it('イベントハンドラに値を渡せること', function(done) {
var $el = $('<div>');
$el.bind('foo', function(event, val) {
expect(val).to.be('bar');
done();
});
$el.trigger('foo', 'bar');
});
});
コールバックが2回呼ばれるかをテスト
describe('jQuery#trigger', function() {
it('イベントハンドラに値を渡せること', function(done) {
var $el = $('<div>');
var count = 0;
$el.bind('foo', function(event, val) {
count++;
if (count === 1) {
expect(val).to.be('bar');
}
else if (count === 2) {
expect(val).to.be('baz');
done();
}
});
$el.trigger('foo', 'bar');
$el.trigger('foo', 'baz');
});
});
sinon.spy
var spy = sinon.spy();
spy('foo', 'bar');
spy('hoge');
spy.callCount; //=> 2
spy.args; //=> [ ['foo', 'bar'], ['hoge'] ]
sinon.spyを使ったテスト
describe('jQuery#trigger', function() {
it('イベントハンドラに値を渡せること', function() {
var $el = $('<div>');
var spy = sinon.spy();
$el.bind('foo', spy);
$el.trigger('foo', 'bar');
$el.trigger('foo', 'baz');
// 呼ばれた回数を検証
expect(spy.callCount).to.be(2);
// 引数を検証
expect(spy.args[0][1]).to.be('bar');
expect(spy.args[1][1]).to.be('baz');
});
});
何か処理をして要素を削除する関数
function removeElement($el) {
// 何か処理
$el.remove();
}
jQuery.fn.removeが呼ばれるかをテスト
describe('removeElement', function() {
it('jQuery.fn.removeが呼ばれること', function() {
var $el = $('<div>');
var spy = sinon.spy(jQuery.fn, 'remove');
removeElement($el);
// jQuery.fn.removeが呼ばれていることを確認
expect(spy.callCount).to.be(1);
spy.restore();
});
});
confirmで確認して要素を削除する関数
function removeElement($el) {
if (window.confirm('削除しますか?')) {
$el.remove();
}
}
stubを使ったテスト
describe('removeElement', function() {
it('confirmがtrueだった場合removeが呼ばれること', function() {
var $el = $('<div>');
var spy = sinon.spy(jQuery.fn, 'remove');
// window.confirmをstub化
var stub = sinon.stub(window, 'confirm');
// window.confirmはtrueを返す
stub.returns(true);
removeElement($el);
expect(spy.callCount).to.be(1);
spy.restore();
stub.restore();
});
});
window.confirmがfalseを返す場合
describe('removeElement', function() {
it('confirmがfalseだった場合removeが呼ばれないこと', function() {
var $el = $('<div>');
var spy = sinon.spy(jQuery.fn, 'remove');
// window.confirmをstub化
var stub = sinon.stub(window, 'confirm');
// window.confirmはfalseを返す
stub.returns(false);
removeElement($el);
expect(spy.callCount).to.be(0);
spy.restore();
stub.restore();
});
});
とあるModelクラス
Model.prototype.set = function(key, val) {
var error = this.validate(key, val);
if (!error) {
this.attr[key] = val;
}
};
Model.prototype.validate = function() { ... };
mockを使ったテスト
describe('Model#set', function() {
it('validateが呼ばれること', function() {
var model = new Model();
// 期待するvalidateの振る舞いを先に記述する
var mock = sinon.mock(model);
mock.expects('validate')
.once()
.withArgs('foo', 'bar')
.returns(null);
model.set('foo', 'bar');
// fooに値がセットされている
expect(model.attr.foo).to.be('bar');
// 期待通り呼ばれたかをチェック
mock.verify();
});
});
今日が日曜日だったらtrueを返す関数
function isSunday() {
var now = new Date();
return now.getDay() === 0; // 0は日曜日
}
sinon.useFakeTimersを使ったテスト
describe('isSunday()', function() {
it('日曜日の場合にtrueを返すこと', function() {
var sunday = new Date('2013-03-24'); // この日は日曜日
var clock = sinon.useFakeTimers(sunday.getTime());
expect(isSunday()).to.be(true);
clock.restore();
});
it('日曜日以外の場合にfalseを返すこと', function() {
var monday = new Date('2013-03-25'); // この日は月曜日
var clock = sinon.useFakeTimers(monday.getTime());
expect(isSunday()).to.be(false);
clock.restore();
});
});
10分後にコールバックを実行する関数
function wait10min(fn) {
var time = 10 * 60 * 1000; // 10分
setTimeout(function() {
fn();
}, time);
}
タイマーを操作することができる
describe('wait10min()', function() {
it('10分後にコールバックが呼ばれること', function() {
var clock = sinon.useFakeTimers();
var spy = sinon.spy();
wait10min(spy);
// 10分の1ミリ秒手前まで時間を進める
clock.tick(10 * 60 * 1000 - 1);
// まだコールバックは呼ばれていない
expect(spy.called).to.be(false);
// 1ミリ秒進める
clock.tick(1);
// コールバックが呼ばれた
expect(spy.called).to.be(true);
clock.restore();
});
});
ユーザーAPIを呼び出す関数
function fetchUser(fn) {
$.ajax({
type: 'GET',
url: '/api/user',
dataType: 'json',
})
.done(function(userData) {
fn({ result: 'ok', data: userData });
});
}
XHRを置き換えてテストする
describe('fetchUser', function() {
it('userAPIにアクセスすること', function() {
// XHRを置き換える
var xhr = sinon.useFakeXMLHttpRequest();
// HTTPリクエストがあったらリクエストオブジェクトを保存する
var requests = [];
xhr.onCreate = function(request) {
requests.push(request);
};
// fetchUserを呼ぶ
var spy = sinon.spy();
fetchUser(spy);
// 期待通りリクエストされているか
var request = requests[0];
expect(request.method).to.be('GET');
expect(request.url).to.be('/api/user');
// 任意のレスポンスを返す
request.respond('200', {}, '{"foo":"bar"}');
// レスポンスがあったときに期待通り処理が行われているか
expect(spy.callCount).to.be(1);
expect(spy.args[0][0]).to.eql({
result: 'ok',
data: { foo: 'bar' }
});
// XHRをもとに戻す
xhr.restore();
});
});
fakeServerを使う
describe('fetchUser', function() {
it('userAPIにアクセスすること', function() {
// XHRを置き換えてダミーサーバーを作る
var server = sinon.fakeServer.create();
// ダミーサーバーのリクエストを処理する
var response = [ 200, {}, '{"foo":"bar"}' ];
server.respondWith('GET', '/api/user', response);
// fetchUserを呼ぶ
var spy = sinon.spy();
fetchUser(spy);
// レスポンスを返す
server.respond();
// レスポンスがあったときに期待通り処理が行われているか
expect(spy.callCount).to.be(1);
expect(spy.args[0][0]).to.eql({
result: 'ok',
data: { foo: 'bar' }
});
// XHRをもとに戻す
server.restore();
});
});