Episode inti ini mengajarkan struktur test unit: deklarasi dengan test dan describe, matchers dasar seperti toBe, toEqual, toContain, dan toBeTruthy, siklus setup teardown dengan beforeEach dan kawan-kawan, serta cara menguji pure functions dan edge cases.

Inilah episode yang paling sering dipraktikkan dalam karir testing: menulis test unit. Episode 4 mengajarkan cara menyusun test yang jelas, memakai matchers dengan tepat, mengatur siklus hidup setup dan teardown, serta menguji pure functions sampai edge cases-nya.
Sebuah test unit yang baik bukan sekadar "kode yang lolos". Dia adalah dokumen hidup: membaca namanya harus menjelaskan perilaku yang dijamin, dan gagalnya harus langsung menunjuk ke bagian kode yang bermasalah. Mari bangun kebiasaan itu mulai sekarang.
Dua fungsi inti untuk menyusun test: test (atau aliasnya it) untuk mendeklarasikan satu test, dan describe untuk mengelompokkan test yang berkaitan:
describe("fungsi tambah", () => {
test("menjumlahkan dua angka positif", () => {
expect(1 + 2).toBe(3);
});
test("menjumlahkan angka nol", () => {
expect(5 + 0).toBe(5);
});
});describe("fungsi tambah", () => {}) membuat blok pengelompok, dan test("...", () => {}) mendeklarasikan satu test dengan nama yang deskriptif. Nama test adalah kontrak perilaku — tulis seolah-olah dibaca orang lain.
Hindari nama seperti "test satu" atau "test dua". Gunakan pola yang menjelaskan kondisi dan hasil: "menjumlahkan dua angka positif", "mengembalikan error saat argumen kosong". Nama yang baik membuat output --verbose terasa seperti daftar spesifikasi.
Matcher toBe memakai Object.is untuk membandingkan nilai primitif, sedangkan toEqual membandingkan struktur secara rekursif. Perbedaan ini penting:
test("toBe untuk primitif", () => {
expect(2 + 2).toBe(4);
expect("hello").toBe("hello");
});
test("toEqual untuk objek", () => {
const data = { nama: "arif", umur: 30 };
expect(data).toEqual({ nama: "arif", umur: 30 });
});
test("toBe gagal untuk objek", () => {
expect({ a: 1 }).not.toBe({ a: 1 });
});expect(data).toEqual({ nama: "arif", umur: 30 }) membandingkan isi objek, bukan referensinya. Objek dua instance yang isinya sama dianggap toEqual tetapi tidak toBe, karena toBe membandingkan referensi memori.
Matcher lain yang sering dipakai:
toContain: memeriksa elemen di dalam array atau substring di dalam string.toBeTruthy dan toBeFalsy: memeriksa nilai yang dianggap benar atau salah dalam konteks boolean.toBeNull, toBeDefined, toBeGreaterThan, toHaveLength: matcher spesifik sesuai kebutuhan.test("toContain pada array dan string", () => {
expect(["apel", "mangga"]).toContain("mangga");
expect("selamat datang").toContain("datang");
});
test("toBeTruthy dan toBeFalsy", () => {
expect("teks").toBeTruthy();
expect(0).toBeFalsy();
expect(null).toBeFalsy();
});Perhatikan expect(0).toBeFalsy() — angka nol dianggap falsy, begitu juga string kosong dan null. Memahami konversi boolean ini mencegah banyak kebingungan saat menulis assertion.
Jest menyediakan empat fungsi untuk mengatur lingkungan test:
beforeAll: berjalan sekali sebelum semua test dalam blok.beforeEach: berjalan sebelum setiap test.afterEach: berjalan setelah setiap test.afterAll: berjalan sekali setelah semua test selesai.describe("database test", () => {
let daftar;
beforeEach(() => {
daftar = [];
});
afterEach(() => {
daftar = null;
});
test("menambah satu item", () => {
daftar.push("item");
expect(daftar).toHaveLength(1);
});
test("dimulai kosong", () => {
expect(daftar).toHaveLength(0);
});
});Karena beforeEach(() => { daftar = []; }) menjalankan ulang inisialisasi sebelum tiap test, urutan eksekusi antar test tidak saling memengaruhi — fondasi utama untuk test yang deterministik.
Pure function — fungsi yang outputnya hanya bergantung pada input dan tidak memiliki efek samping — adalah kandidat ideal untuk unit test. Setiap kombinasi input yang menarik bisa menjadi satu test:
function bagi(a, b) {
if (b === 0) {
throw new Error("pembagi tidak boleh nol");
}
return a / b;
}
describe("fungsi bagi", () => {
test("pembagian normal", () => {
expect(bagi(10, 2)).toBe(5);
});
test("hasil pecahan", () => {
expect(bagi(1, 4)).toBeCloseTo(0.25);
});
test("pembagi nol melempar error", () => {
expect(() => bagi(1, 0)).toThrow("pembagi tidak boleh nol");
});
});Ada tiga hal menarik di sini. Pertama, expect(bagi(1, 4)).toBeCloseTo(0.25) dipakai karena aritmetika float JavaScript tidak presisi untuk perbandingan toBe. Kedua, untuk menguji error, expect(() => bagi(1, 0)).toThrow(...) membungkus pemanggilan dalam fungsi — tanpa itu error akan tertangkap dan test salah gagal. Ketiga, edge case pembagi nol justru diuji karena di sanalah perilaku yang paling penting.
Episode 4 adalah tulang punggung seluruh series: kalian sekarang bisa menyusun test dengan test dan describe, memilih matcher yang tepat antara toBe, toEqual, toContain, dan toBeTruthy, mengelola lingkungan test dengan siklus hidup setup teardown, serta menguji pure functions beserta edge cases-nya.
Inti yang harus dibawa pulang:
toBe untuk primitif, toEqual untuk struktur objek.toContain untuk array dan string; pahami falsy dan truthy.beforeEach dan afterEach menjaga setiap test tetap terisolasi.toBeCloseTo, bukan toBe.toThrow.Di episode 5 selanjutnya kita akan masuk ke mocking dan spying — jest.fn() dan jest.spyOn(), perbedaan manual mocks dan automatic mocks, mocking modul dengan jest.mock(), serta cara mengendalikan implementasi dan perilaku reset. Inilah kunci untuk menguji kode yang bergantung pada sistem eksternal.