Laravel HTTP Feature Unit Tests Pest Database Refresh
Definisi
Di Laravel, HTTP testing adalah cara menguji request/response lewat layer aplikasi yang nyata, misalnya route, controller, middleware, validasi, redirect, session, dan JSON response. Ini bukan sekadar memanggil method, tetapi menjalankan alur request seperti client sungguhan, dengan test helper Laravel yang meniru request HTTP.
Feature test dipakai untuk menguji alur pengguna atau alur request yang melibatkan beberapa lapisan sekaligus. Unit test dipakai untuk menguji logika kecil yang bisa berdiri sendiri, tanpa harus menjalankan banyak komponen aplikasi.
Pest adalah test runner dan test DSL yang membuat test Laravel lebih ringkas dan enak dibaca. Di atas Laravel test case, Pest memberi syntax yang lebih pendek, assertion yang tetap lengkap, dan gaya penulisan yang cocok untuk test file yang padat.
Database refresh biasanya merujuk ke cara menyiapkan database test agar selalu bersih dan konsisten antar test run. Di Laravel, ini paling sering dilakukan dengan trait seperti RefreshDatabase, atau dengan strategi reset database lain yang setara.
Kapan pakai feature test vs unit test
Kalau kamu bingung harus menaruh test di mana, pakai aturan praktis ini:
Pakai feature test kalau yang ingin kamu verifikasi adalah perilaku aplikasi dari luar
Feature test cocok untuk hal seperti:
- request masuk ke route yang benar
- middleware auth atau policy ikut bekerja
- validation memblokir input yang salah
- data benar-benar tersimpan ke database
- response HTML atau JSON sesuai harapan
- redirect, flash session, dan status code benar
- integrasi antar beberapa layer berjalan
Feature test paling berguna saat kamu ingin menjawab pertanyaan: “Kalau user melakukan X, aplikasi benar-benar melakukan Y?”
Pakai unit test kalau yang ingin kamu verifikasi adalah logika kecil yang terisolasi
Unit test cocok untuk:
- formatter atau helper kecil
- service class dengan input-output yang jelas
- perhitungan harga, diskon, status, atau mapping data
- aturan domain yang bisa diuji tanpa HTTP layer
- transformasi array atau object yang tidak perlu route/controller
Unit test paling berguna saat kamu ingin menjawab pertanyaan: “Fungsi ini, kalau diberi input tertentu, apakah hasilnya benar?”
Rule of thumb yang praktis
- Kalau ada request, response, middleware, auth, atau DB integration, condong ke feature test.
- Kalau logikanya murni dan bisa diuji tanpa bootstrapping aplikasi besar, condong ke unit test.
- Kalau kamu harus banyak mock supaya test bisa jalan, mungkin test itu terlalu tinggi atau terlalu rendah levelnya.
- Kalau test mulai meniru browser atau request dari luar, itu sinyal feature test.
Jangan salah pilih level test
Kesalahan umum adalah menaruh semua hal di unit test hanya karena terlihat lebih cepat. Akibatnya, test jadi banyak mock dan tidak lagi mewakili cara aplikasi benar-benar bekerja.
Sebaliknya, jangan semua test dipaksa jadi feature test. Kalau logika cuma hitungan sederhana, feature test malah bikin test lebih lambat dan lebih rapuh tanpa nilai tambahan yang sepadan.
Bagaimana request testing di Laravel bekerja
Laravel request testing memakai helper seperti get(), post(), put(), patch(), delete(), dan json() untuk mensimulasikan request ke aplikasi.
Yang terjadi bukan request sungguhan dari browser, tetapi Laravel membangun request internal, melewatinya melalui routing, middleware, controller, validation, dan response layer. Karena itu, kamu bisa menguji hampir seluruh alur tanpa membuka browser.
Alur dasarnya
Urutan pentingnya begini:
- test membuat request
- Laravel menyalurkan request ke route
- middleware ikut dieksekusi kalau terpasang
- controller atau closure menangani request
- response keluar
- test melakukan assertion terhadap hasilnya
Contoh request test
it('can store a post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'title' => 'Halo Laravel',
'body' => 'Isi artikel',
]);
$response->assertRedirect('/posts');
$this->assertDatabaseHas('posts', [
'title' => 'Halo Laravel',
'user_id' => $user->id,
]);
});Contoh di atas menunjukkan bahwa HTTP testing biasanya memeriksa lebih dari satu hal:
- response redirect atau status code
- efek samping di database
- context user yang sedang login
Request testing untuk JSON API
Untuk API, biasanya pakai getJson(), postJson(), dan kawan-kawannya.
it('returns validation error for invalid payload', function () {
$response = $this->postJson('/api/posts', [
'title' => '',
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['title']);
});Di sini yang diuji adalah kontrak API, bukan sekadar “method berhasil dipanggil”.
Cara kerja assertion di test Laravel
Pola dasar testing tetap sama: arrange → act → assert.
Arrange
Siapkan semua hal yang dibutuhkan test:
- model factory
- user login
- data seed tertentu
- mock atau fake jika perlu
- state awal database
Act
Jalankan aksi yang sedang diuji:
- kirim request HTTP
- panggil method service
- jalankan command atau job bila relevan
Assert
Periksa hasilnya:
- status code
- redirect target
- session flash
- JSON fragment
- database state
- event atau notification
Pola ini penting supaya test mudah dibaca. Kalau arrange, act, dan assert bercampur, test cenderung sulit dirawat.
Pest di Laravel
Pest sering dipakai karena sintaksnya lebih ringkas daripada PHPUnit murni, tetapi tetap berjalan di atas ekosistem Laravel yang sama.
Contoh gaya Pest
it('lists posts', function () {
Post::factory()->count(3)->create();
$response = $this->get('/posts');
$response->assertOk();
});Contoh data provider di Pest
it('rejects invalid titles', function (string $title) {
$this->post('/posts', [
'title' => $title,
'body' => 'Isi',
])->assertSessionHasErrors('title');
})->with([
'',
'a',
]);Kapan Pest lebih enak
Pest terasa lebih nyaman ketika:
- kamu ingin test file yang pendek dan fokus
- banyak test kecil yang gaya penulisannya repetitif
- tim ingin syntax yang lebih ringan dibaca
Kapan PHPUnit biasa masih cocok
PHPUnit masih sangat valid kalau:
- tim sudah punya kebiasaan lama di PHPUnit
- kamu butuh struktur class-based yang eksplisit
- test suite besar sudah ada dan tidak ingin migrasi penuh
Intinya, Pest bukan mengganti konsep testing. Pest hanya mengubah cara menulisnya.
Database refresh di test Laravel
Test yang menyentuh database harus dimulai dari state yang dapat diprediksi. Di Laravel, cara paling umum adalah memakai RefreshDatabase.
Apa yang dilakukan RefreshDatabase
Trait ini memastikan database test disiapkan ulang agar setiap test berjalan dengan kondisi yang bersih.
Biasanya hasilnya:
- migration dijalankan untuk database test
- state antar test tidak bocor
- test lebih stabil
- data sisa dari test sebelumnya tidak mengganggu
Contoh pemakaian
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('creates a post', function () {
$user = User::factory()->create();
$this->actingAs($user)->post('/posts', [
'title' => 'Artikel baru',
'body' => 'Konten',
])->assertRedirect();
$this->assertDatabaseCount('posts', 1);
});Kenapa database refresh penting
Tanpa reset database yang konsisten, test bisa lolos di laptopmu tapi gagal di CI, atau sebaliknya. Ini terjadi karena test bergantung pada data sisa, urutan eksekusi, atau state yang tidak sengaja terbentuk sebelumnya.
Pilihan umum
RefreshDatabase→ pilihan default yang paling aman untuk banyak proyekDatabaseTransactions→ bisa cepat, tapi tidak cocok untuk semua kasus, terutama jika ada proses yang butuh commit nyata- seeding terkontrol → dipakai kalau test butuh referensi data yang sama
Alur request testing yang sehat
Pola ini biasanya paling efektif untuk test production app:
- request diuji lewat helper Laravel
- efek penting diuji lewat assertion yang relevan
- database di-reset supaya hasil konsisten
Jebakan umum pada test yang lambat atau rapuh
1. Terlalu banyak test yang menyentuh database tanpa kebutuhan
Database test memang wajar, tetapi kalau semua logika kecil ikut masuk feature test, suite akan terasa lambat.
Solusi praktis:
- pindahkan logika murni ke unit test
- pakai feature test hanya untuk alur yang benar-benar perlu integrasi
- hindari setup database yang terlalu besar di setiap test
2. Factory terlalu berat
Factory yang saling membuat banyak relasi bisa membuat test lambat.
Contoh masalah:
- nested factory terlalu dalam
- observer atau event ikut jalan padahal tidak perlu
- model factory menghasilkan banyak data yang tidak dipakai
Solusi:
- buat state yang spesifik
- isi atribut minimum yang diperlukan
- matikan hal sampingan kalau tidak sedang dites
3. Assertion yang terlalu longgar
Test yang hanya cek assertOk() sering terlalu lemah. Test seperti itu bisa tetap hijau walaupun isi response salah.
Lebih baik cek hal yang benar-benar penting:
- status code
- redirect target
- fragment JSON
- record database
- error validation
4. Mengandalkan urutan test
Kalau test A harus jalan dulu baru test B lulus, berarti ada state bocor.
Ini tanda buruk. Test seharusnya bisa dijalankan sendiri-sendiri dan tetap konsisten.
5. Mock berlebihan
Mock itu berguna, tetapi terlalu banyak mock membuat test tidak lagi memverifikasi perilaku nyata.
Tanda-tandanya:
- test panjang tapi tidak memberi keyakinan tambahan
- refactor kecil bikin banyak test rusak
- kamu lebih sering memperbaiki mock daripada logic
6. Test menyentuh UI atau external service padahal tidak perlu
Kalau tujuan test adalah request Laravel, jangan tambahkan beban yang tidak relevan:
- panggilan API eksternal
- file system yang tidak perlu
- queue asynchronous yang tidak diuji langsung
Gunakan fake, stub, atau assert terpisah bila memang diperlukan.
7. Nama test tidak menjelaskan perilaku
Nama test yang kabur membuat suite sulit dipelihara.
Lebih baik:
it('stores a post for authenticated users')it('rejects empty title')it('returns post list as JSON')
Daripada nama generik yang tidak menjelaskan hasil.
Praktik yang biasanya paling sehat
- Pakai feature test untuk alur HTTP, auth, validation, dan database integration.
- Pakai unit test untuk logika murni yang kecil dan deterministik.
- Pakai Pest kalau ingin syntax ringkas dan readable.
- Pakai
RefreshDatabaseuntuk menjaga state test tetap bersih. - Jangan memaksakan semua test jadi feature test atau semua jadi unit test.
- Fokus pada perilaku yang paling penting di production, bukan sekadar coverage angka.
Contoh struktur test yang rapi
it('creates a post through the web form', function () {
// arrange
$user = User::factory()->create();
// act
$response = $this->actingAs($user)->post('/posts', [
'title' => 'Judul',
'body' => 'Isi artikel',
]);
// assert
$response->assertRedirect('/posts');
$this->assertDatabaseHas('posts', [
'title' => 'Judul',
'user_id' => $user->id,
]);
});Struktur seperti ini paling mudah dipindai, paling mudah direview, dan paling kecil kemungkinan jadi test yang rapuh.
Ringkasan cepat
- Feature test: cocok untuk alur request, response, auth, validation, dan integrasi database.
- Unit test: cocok untuk logika kecil yang tidak butuh HTTP layer.
- Pest: syntax lebih singkat, tetap jalan di ekosistem Laravel yang sama.
- Request testing: Laravel mengeksekusi request internal lewat route, middleware, controller, lalu kamu assertion hasilnya.
- RefreshDatabase: menjaga database test tetap bersih dan konsisten.
- Jebakan umum: test terlalu berat, factory terlalu mahal, assertion terlalu longgar, dan state bocor antar test.