Test adalah kode kecil yang menjalankan kodemu lalu memeriksa hasilnya secara otomatis. Daripada buka browser dan klik-klik tiap habis mengubah sesuatu, kamu jalankan satu perintah, dan dalam hitungan detik kamu tahu mana yang masih benar dan mana yang rusak. Bayangin warung bakso yang mau memastikan semangkuknya enak. Kokinya bisa mencicipi kuah, bakso, dan mie satu per satu: cepat, dan kalau ada yang aneh langsung ketahuan bagian mana. Bisa mencicipi kuah dan bakso bareng, untuk tahu cocok atau gak kalau dicampur. Atau menyajikan semangkuk penuh ke pelanggan lalu menunggu reaksinya: paling nyata, tapi lama, dan kalau pelanggan bilang "kurang enak", kamu belum tahu salahnya di mana. Tiga cara itu persis tiga level testing.
🧪 Tebakan cepat: 100 unit test yang masing-masing butuh 5 ms, atau 10 E2E test yang masing-masing butuh 8 detik. Mana yang selesai duluan, dan selisihnya berapa? Jawabannya ada di bagian "Kenapa bentuknya piramida".
Test pertamamu
Contohnya toko online. Fungsi ini menghitung total belanja setelah diskon:
function hitungTotal(items, diskonPersen) {
const subtotal = items.reduce((jumlah, item) => jumlah + item.harga * item.qty, 0);
return subtotal - (subtotal * diskonPersen) / 100;
}
describe("hitungTotal", () => {
it("memotong diskon 10%", () => {
expect(hitungTotal([{ harga: 50000, qty: 2 }], 10)).toBe(90000);
});
});
describe("hitungTotal", ...)mengelompokkan test yang membahas satu hal. Isinya function berisi test-test-nya.it("memotong diskon 10%", ...)adalah satu test: nama yang dibaca manusia, lalu function berisi pemeriksaannya.test(...)sama persis denganit(...).expect(hitungTotal(...))menjalankan kodemu dan memegang hasilnya..toBe(90000)membandingkan hasil itu dengan yang kamu harapkan. Sama: test lulus (pass). Beda: test gagal (fail).
Yang di dalam expect(...) harus pemanggilan fungsinya. expect(90000).toBe(90000) selalu lulus, karena gak ada kode yang benar-benar diperiksa.
Kalau suatu hari baris terakhirnya salah ketik jadi return subtotal - diskonPersen;, hasilnya 99.990, dan test runner (program yang menjalankan semua test, misalnya Vitest) melapor:
× checkout.test.js > hitungTotal > memotong diskon 10% 2ms
→ expected 99990 to be 90000 // Object.is equality
Untuk fungsi async (yang menjawab lewat Promise, misalnya mengambil data dari database), beri async pada function test-nya dan tunggu hasilnya dengan await:
async function ambilPesanan(id) {
return { id, total: 90000 };
}
describe("ambilPesanan", () => {
it("mengembalikan pesanan dengan id yang diminta", async () => {
const pesanan = await ambilPesanan(7);
expect(pesanan.id).toBe(7);
});
});
Tanpa await, pesanan masih berupa Promise, pesanan.id jadi undefined, dan test-nya gagal dengan expected undefined to be 7.
Tiga level: unit, integration, E2E
| Level | Yang diuji | Contoh di toko online | Kata yang sering ada di nama test-nya | Waktu per test |
|---|---|---|---|---|
| Unit | satu fungsi, sendirian | hitungTotal, formatRupiah |
fungsi, hitung, format | sekitar 2 ms |
| Integration | interaksi antar komponen atau modul | route POST /api/orders yang memvalidasi lalu menyimpan ke database tes |
API, endpoint, database, controller | ratusan ms |
| E2E | alur lengkap dari sisi pengguna, lewat browser sungguhan | buka keranjang, klik Bayar, lihat total | klik, form, browser, halaman | beberapa detik |
- Unit test menguji satu fungsi, terpisah dari server, database, dan browser. Kalau gagal, namanya langsung menunjuk fungsi yang salah.
- Integration test menguji beberapa bagian yang bekerja bareng: route API benar-benar memanggil validasi, menghitung total, dan menyimpan ke database khusus test.
- E2E test (end-to-end, dari ujung ke ujung) membuka browser sungguhan, biasanya pakai Playwright, lalu mengisi form dan mengklik tombol seperti pengguna:
test("pembeli klik Bayar dan melihat total", async ({ page }) => {
await page.goto("/keranjang");
await page.getByRole("button", { name: "Bayar" }).click();
await expect(page.getByText("Total: Rp90.000")).toBeVisible();
});
Ini yang terjadi kalau satu bug ditanam di toko yang sama, dengan 7 unit, 2 integration, dan 1 E2E test:
| Bug yang ditanam | Unit (7) | Integration (2) | E2E (1) |
|---|---|---|---|
hitungTotal mengurangi 10 rupiah, bukan 10% |
2 gagal, dua-duanya di hitungTotal |
lulus | gagal: Total: Rp90.000 gak muncul |
| route API crash saat menyimpan | lulus | 1 gagal: expected 500 to be 201 |
gagal |
| tombol Bayar lupa dipasangi handler | lulus | lulus | gagal |
Unit test menunjuk persis fungsi yang salah. E2E menangkap ketiganya, termasuk bug tombol yang gak kelihatan dari level lain, tapi pesannya cuma "total yang diharapkan gak muncul", jadi kamu masih harus mencari sendiri sumbernya.
Kenapa bentuknya piramida
Piramida testing adalah panduan proporsi: banyak test murah di dasar, sedikit test mahal di puncak. Patokan umumnya 70% unit, 20% integration, 10% E2E. Dari 10 test: 7 unit, 2 integration, 1 E2E.
Alasannya waktu dan kejelasan. 100 unit test × 5 ms = 0,5 detik. 10 E2E test × 8 detik = 80 detik. Unit test selesai duluan, selisihnya 79,5 detik, dan itu terasa tiap kali kamu menyimpan file. Itu jawaban tebakannya.
Jadi tulis unit test untuk logika (hitungan, validasi, format), integration test untuk tiap endpoint penting, dan E2E cuma untuk alur kritis: login, checkout, pendaftaran.
⚠️ Jebakan yang sering ketemu
- Cuma pakai E2E. Test-nya lambat, dan saat gagal sulit menemukan sumber bug-nya. E2E melengkapi unit test, bukan menggantikannya.
- Memeriksa angka yang ditulis langsung.
expect(5).toBe(5)gak menguji apa pun. Yang diperiksa selalu hasil pemanggilan:expect(add(2, 3)).toBe(5). - Cuma mengetes jalan mulus. Bug sering ada di keranjang kosong, angka negatif, atau input aneh. Tambah test untuk
add(4, -5)atau keranjang kosong, bukan cumaadd(2, 3). - Lupa
await. Fungsi async yang gak ditunggu menghasilkan Promise, bukan datanya.
TL;DR: Test adalah kode yang menjalankan kodemu dan memeriksa hasilnya otomatis: describe mengelompokkan, it atau test adalah satu test, expect(hasil).toBe(harapan) membandingkan, dan fungsi async ditunggu dengan await. Ada tiga level: unit (satu fungsi, paling cepat, paling banyak, di dasar piramida), integration (interaksi antar komponen, misalnya API dan database), dan E2E (alur pengguna di browser, paling lambat dan mahal, di puncak). Patokannya 70% unit, 20% integration, 10% E2E. Kalau cuma mengandalkan E2E, test-nya lambat dan sulit menunjuk sumber bug.