Integration Test: Menangkap Masalah Saat Komponen Saling Berkomunikasi

Kamu menulis sebuah fungsi yang terlihat benar. Unit test lolos. Logikanya bersih. Lalu kamu deploy, dan aplikasi mulai mengembalikan error. Kolom database yang kamu asumsikan ada ternyata sudah diubah namanya minggu lalu. API eksternal yang kamu panggil mengubah format responsnya. Service yang kamu andalkan sekarang mengharapkan header yang berbeda.

Inilah celah yang tidak bisa diisi oleh unit test. Sebuah fungsi bisa saja sempurna secara terisolasi tetapi tetap gagal saat mencoba berkomunikasi dengan komponen lain. Integration test hadir untuk menangkap masalah-masalah semacam ini.

Apa yang Sebenarnya Diperiksa oleh Integration Test

Integration test memverifikasi bahwa dua atau lebih komponen bekerja bersama dengan benar. Komponen-komponen tersebut bisa berupa aplikasi dan database, service dan API eksternal, atau dua service internal dalam sistem yang sama.

Bug yang mereka tangkap jarang tentang logika yang salah. Mereka tentang asumsi yang tidak cocok:

Diagram sekuens berikut mengilustrasikan ketidakcocokan tipikal: sebuah service mengirim tanggal sebagai string, tetapi database mengharapkan timestamp, yang menyebabkan error.

sequenceDiagram participant Service participant Database Service->>Database: INSERT INTO orders (date) VALUES ('2025-04-01') Database-->>Service: ERROR: column "date" is of type timestamp but expression is of type text Note over Service,Database: Asumsi tidak cocok: string vs. timestamp
  • Kode kamu mengirim tanggal sebagai string, tetapi kolom database mengharapkan timestamp.
  • Service kamu memanggil API dengan parameter query, tetapi API tersebut memindahkan parameter itu ke dalam request body.
  • Aplikasi kamu mengasumsikan sebuah field selalu ada, tetapi service upstream hanya menyertakannya dalam kondisi tertentu.

Ini bukan bug yang bisa kamu temukan hanya dengan melihat kode. Mereka baru muncul saat komponen benar-benar bertukar data.

Jebakan Kerapuhan (Fragility Trap)

Integration test punya reputasi lambat dan rapuh. Reputasi ini memang pantas. Semakin banyak komponen nyata yang kamu libatkan, semakin besar kemungkinan test gagal karena alasan di luar kode kamu. Timeout jaringan. Service dependen yang sedang down. Data test yang rusak karena eksekusi sebelumnya.

Ketika ini terjadi berulang kali, tim mulai tidak percaya pada hasil test. Mereka mulai melewati integration test atau mengabaikan kegagalan. Test menjadi noise, bukan sinyal.

Solusinya bukan menghindari integration test. Solusinya adalah selektif tentang apa yang kamu uji dengan dependensi nyata.

Memilih Apa yang Akan Diuji dengan Dependensi Nyata

Tidak semua dependensi perlu menjadi nyata dalam integration test kamu. Aturan praktisnya sederhana: uji dengan dependensi nyata hanya untuk hal-hal yang sulit disimulasikan atau yang sering menyebabkan masalah di produksi.

Database biasanya layak diuji dengan instance nyata. Perilaku query, constraint, transaksi, dan locking sulit di-mock secara akurat. Mock mungkin memberi tahu kamu bahwa query kamu sintaksisnya benar, tetapi tidak akan memberi tahu bahwa query kamu menyebabkan deadlock saat akses bersamaan, atau bahwa migrasi kamu mengubah tipe kolom yang masih diperlakukan sebagai string oleh kode kamu.

API eksternal pihak ketiga umumnya tidak layak diuji dengan endpoint nyata di pipeline kamu. Gunakan test double yang merekam respons tipikal. Risiko test yang flaky karena masalah jaringan atau rate limit API lebih besar daripada manfaatnya. Simpan integrasi nyata untuk staging atau verifikasi produksi.

Service internal dalam organisasi kamu berada di tengah-tengah. Kamu bisa menguji dengan instance nyata jika antarmuka sering berubah dan biaya ketidakcocokan tinggi. Jika tidak, contract test sering memberikan sinyal yang lebih baik dengan kerapuhan yang lebih rendah.

Cara praktis untuk memutuskan: tanyakan pada diri sendiri, "Jika dependensi ini bermasalah, apakah saya akan tahu dari logika aplikasi, atau hanya dari cara ia terhubung?" Jika jawabannya "dari cara ia terhubung" -- format respons, struktur header, urutan parameter -- maka itu adalah kandidat untuk integration test dengan dependensi nyata. Jika jawabannya "dari logika aplikasi," unit test atau contract test sudah cukup.

Menjaga Integration Test Tetap Cepat dan Andal

Setelah kamu memutuskan apa yang akan diuji dengan dependensi nyata, ikuti praktik-praktik ini untuk menjaga integration test kamu tetap berguna:

Uji hanya koneksinya, bukan logika bisnisnya. Jika kamu sudah memiliki unit test yang mencakup aturan bisnis kamu, jangan ulangi di integration test. Integration test untuk query database harus memverifikasi bahwa query berjalan sukses terhadap database nyata dan mengembalikan struktur yang diharapkan. Ia tidak harus memverifikasi setiap edge case dari logika bisnis yang menggunakan query tersebut.

Reset lingkungan sebelum setiap test. Jika kamu menggunakan database, buat data test yang terisolasi dan bersihkan setelah test selesai. Test yang bergantung pada status yang ditinggalkan oleh test sebelumnya bersifat rapuh dan sulit di-debug. Gunakan transaksi database yang roll back setelah setiap test, atau spin up container test baru untuk setiap eksekusi test.

Batasi jumlah integration test. Kamu tidak perlu menguji setiap kombinasi parameter. Uji satu happy path dan beberapa skenario kegagalan yang realistis. Tujuannya adalah keyakinan bahwa koneksi berfungsi, bukan cakupan dari setiap input yang mungkin.

Di Mana Integration Test Cocok dalam Pipeline Kamu

Integration test berada di antara unit test dan end-to-end test. Mereka lebih mahal daripada unit test tetapi lebih cepat dan lebih fokus daripada end-to-end test.

Pipeline tipikal menjalankan unit test terlebih dahulu. Jika lolos, pipeline menjalankan integration test. Jika integration test lolos, pipeline melanjutkan ke deployment staging atau produksi. End-to-end test, jika kamu memilikinya, berjalan kemudian atau di lingkungan terpisah.

Tujuan integration test bukan untuk mencapai persentase cakupan. Tujuannya adalah memberi kamu keyakinan bahwa ketika kode kamu berubah, koneksi antar komponen masih berfungsi.

Checklist Praktis

  • Untuk setiap dependensi eksternal, putuskan: uji dengan instance nyata, uji dengan test double, atau andalkan contract test.
  • Jalankan integration test di lingkungan terisolasi yang bisa di-reset ke status yang diketahui.
  • Jaga integration test tetap fokus pada perilaku koneksi, bukan logika bisnis.
  • Batasi integration test menjadi satu happy path dan beberapa skenario kegagalan yang realistis.
  • Pantau waktu eksekusi test. Jika integration test memakan waktu lebih lama dari unit test, kemungkinan kamu memiliki terlalu banyak atau jenis yang salah.

Kesimpulan

Integration test menjawab pertanyaan yang tidak bisa dijawab oleh unit test: "Apakah komponen-komponen ini benar-benar bekerja bersama?" Jawabannya layak didapatkan sebelum kamu deploy. Tetapi integration test adalah alat, bukan tujuan. Bersikaplah selektif tentang apa yang kamu uji dengan dependensi nyata, jaga test tetap cepat dan terisolasi, dan gunakan mereka untuk membangun keyakinan dalam deployment kamu, bukan untuk mengejar angka cakupan.