Best Practice Testing using Sinon.js
Sinon.js adalah tools yang sangat bermanfaat apabila kita ingin melakukan unit test untuk Java Scripts. Sinon menyediakan fitur untuk melakukan Spies, Stubs, dan Mock. Apa saja itu? Mari kita bahas lebih lanjut.
Pengantar
Membuat testing code dengan menggunakan banyak sekali functional lain seperti Ajax, networking, database, dan dependency lainnya tentu saja akan sangat sulit. Misalnya saja apabila aplikasi kita melakukan perintah Ajax atau networking lainnya, kita harus memastikan server yang akan memberikan response pada request kita. Selain itu penggunaan database harus dipastikan bahwa database kita sudah siap untuk memberikan data. Hal ini berarti test yang dibuat akan semakin sulit karena kita harus mempersiapkan environment agar test berjalan lancar.
Sinon.js hadir menyelesaikan masalah yang ada dengan kemampuannya yaitu spies, stubs dan mocks.
Contoh fungsi
Agar mempermudah penjelasan, mari kita mulai dengan ilustrasi fungsi sebagai berikut.
function setupNewUser(info, callback) {
var user = {
name: info.name,
nameLowercase: info.name.toLowerCase()
};
try {
Database.save(user, callback);
}
catch(err) {
callback(err);
}
}
Fungsi diatas akan menerima dua parameter yaitu sebuah objek info berisi data yang akan dimasukkan kedalam database dan sebuah callback function. Dalam kasus ini, fungsi save yang dilakukan dalam Database adalah untuk memasukkan data yang ada kedalam database.
Spies, Stubs, dan Mocks
Spies, Stubs, dan Mocks sama saja dengan pemeran pengganti (stunts) dalam film. Apa yang mereka lakukan adalah sebagai pengganti dari fungsi yang membutuhkan dependency yang ada pada kode kita.
Spies
Spies digunakan untuk memdapatkan informasi mengenai function call. Lebih lanjut, spy dapat memberitahukan berapa banyak sebuah fungsi dipanggil, apa saja argumen yang dipanggil, apa return value dan error yang di throw, dll.
Spy adalah pilihan yang baik untuk test yang ingin melakukan verifikasi terhadap suatu pemanggilan fungsi. Dikombinasikan dengan assertions, kita bisa mengecek berapa banyak perbedaan yang tidak sesuai dengan harapan test kita.
Biasa spies digunakna untuk:
- Mengetahui berapa banyak sebuah fungsi dipanggil
- Mengetahui argumen apa saja yang dipanggil bersamaan dengan fungsi
Ada beberapa fungsi yang bisa digunakan untuk mengecek berapa kali suatu fungsi dipanggil diantaranya:
sinon.assert.callCount
akan menghitung berapa kali fungsi dipanggilsinon.assert.calledOnce
akan memastikan fungsi dipanggil tepat satu kalisinon.assert.notCalled
akan memastikan fungsi tidak dipanggil
Misalnya berikut adalah test code yang kita miliki:
it('should call save once', function() {
var save = sinon.spy(Database, 'save');
setupNewUser({ name: 'test' }, function() { });
save.restore();
sinon.assert.calledOnce(save);
});
Selain itu, untuk mengetahui argumen apa yang dipanggil bersamaan dengan fungsi kita bisa menggunakan sinon.assert.calledWith
. Berikut adalah contohnya:
it('should pass object with correct values to save', function() {
var save = sinon.spy(Database, 'save');
var info = { name: 'test' };
var expectedUser = {
name: info.name,
nameLowercase: info.name.toLowerCase()
};
setupNewUser(info, function() { });
save.restore();
sinon.assert.calledWith(save, expectedUser);
});
Masih banyak hal lain yang dapat di-assert oleh spy, pada penjelasan ini saya tidak menjelaskan keseluruhan hal yang dapat dilakukan.
Spy tidak akan mengubah behavior dari fungsi, namun apabila kita ingin mengubahnya maka kita bisa menggunakan stub.
Stubs
Stubs sebenarnya menyerupai spies, hanya saja stubs akan menimpa fungsi. Stub bisa saja mengubah behavior dari fungsi seperti mengembalukan nilai tertentu atau mengembalikan error. Stub biasa digunakan untuk:
- Apabila ingin menimpa fungsi yang membutuhkan dependency lain
- Apabila ingin melakukan error trigger
Apabila ingin menimpa fungsi yang membutuhkan dependency lain, seperti Database.save
pada contoh, kita bisa menggunakan stub, sehingga dalam kasus ini, kita tidak perlu memastikan Database
sudah siap digunakan dengan pengaturan yang ada, tetapi akan dibuatkan fungsi pengganti oleh Sinon. Berikut adalah contohnya:
it('should pass object with correct values to save', function() {
var save = sinon.stub(Database, 'save');
var info = { name: 'test' };
var expectedUser = {
name: info.name,
nameLowercase: info.name.toLowerCase()
};
setupNewUser(info, function() { });
save.restore();
sinon.assert.calledWith(save, expectedUser);
});
Dengan menimpa fungsi Database.save
kita tidak perlu lagi bersusah payah memikirkan mengenai kesiapan database dalam menangani test.
Selain itu, apabila ingin melakukan testing terhadap error, kita juga bisa menggunakan stub. Berikut adalah contohnya:
it('should pass the error into the callback if save fails', function() {
var expectedError = new Error('oops');
var save = sinon.stub(Database, 'save');
save.throws(expectedError);
var callback = sinon.spy();
setupNewUser({ name: 'foo' }, callback);
save.restore();
sinon.assert.calledWith(callback, expectedError);
});
Mocks
Setelah mengetahui penjelasan mengenai spies dan stubs, sekarang kita akan berkenalan dengan mocks. Mocks pada dasarnya adalah yang paling sakti diantara spies dan stubs, karena mocks dapat melakukan apa yang dapat dilakukan oleh spies dan stubs. Tapi tentu saja terdapat trade off yang siap dibayar apabila menggunakan mocks, yaitu apabila salah dalam penulisan akan mudah sekali terjadi error pada test yang ada karena kita mendefinisikan ekspektasi di awal. Maksudnya dalam mocks kita bisa saja mendefinisikan multiple assertion yang mana harus dicek secara pasti agar tidak terjadi kesalahan. Langsung saja berikut adalah contoh penggunaan mocks:
it('should pass object with correct values to save only once', function() {
var info = { name: 'test' };
var expectedUser = {
name: info.name,
nameLowercase: info.name.toLowerCase()
};
var database = sinon.mock(Database);
database.expects('save').once().withArgs(expectedUser);
setupNewUser(info, function() { });
database.verify();
database.restore();
});
Dalam kasus ini kita memastikan agar fungsi dipanggil sekali dengan fungsi once()
dan argument yang digunakan harus sesuai dengan fungsi withArgs()
. Jadi dengan mocks, kita dapat menggunakan multiple assertion yang sesuai dengan fungsi yang kita mau test.
Tips
Apabila kita menggunakan fungsi yang sama-sama akan distub dan digunakan dalam beberapa test berbeda, kita dapat melakukan shared stubs dengan fungsi beforeEach()
. Sebagai contoh:
describe('Something', function() {
var save;
beforeEach(function() {
save = sinon.stub(Database, 'save');
});
afterEach(function() {
save.restore();
});
it('should do something', function() {
//you can use the stub in tests by accessing the variable
save.yields('something');
});
});
Yang harus dipastikan adalah setelah pemanggilan afterEach()
harus melakukan clean up fungsi stub dengan perintah restore()
.
Apa yang dilakukan minggu lalu
Terdapat beberapa hal yang saya lakukan minggu lalu yaitu:
- Meningkatkan kualitas testing code agar meningkatkan code coverage (sudah diatas 80%)
- Melakukan refactoring terhadap fungsi yang ada pada job handlers agar tidak terjadi nested callback.
- Melakukan integrasi antara backend, database, dan frontend yang ada.
- Melakukan code review dengan teman-teman.
- Memastikan branch bersih.