September 28, 2026
Testing Laravel dengan Pest & PHPUnit.
Menulis test untuk aplikasi Laravel: struktur Unit vs Feature, konfigurasi database testing, RefreshDatabase, model factories, HTTP & JSON assertions, autentikasi, fakes (Mail, Queue, Http), serta sintaks Pest vs PHPUnit.
Laravel dibangun dengan testing sebagai warga kelas satu: phpunit.xml sudah siap, folder tests/Feature dan tests/Unit sudah ada, dan framework menyediakan helper untuk HTTP request, database, serta fake untuk email, queue, dan HTTP client. Test bisa ditulis dengan Pest (sintaks fungsional yang ringkas) atau PHPUnit (berbasis class) — Pest berjalan di atas PHPUnit, jadi keduanya bisa hidup berdampingan. Catatan ini memakai Laravel 13.
Konsep umum seperti AAA dan mocking ada di Dasar Testing .
1. Unit vs Feature di Laravel
| Folder | Boot aplikasi? | Untuk |
|---|---|---|
tests/Unit | ❌ Tidak | Logika murni: helper, value object, kalkulasi |
tests/Feature | ✅ Ya | HTTP request, database, queue, event — mayoritas test |
Dokumentasi Laravel sendiri menyarankan sebagian besar test berupa feature test, karena memberikan kepercayaan paling tinggi bahwa sistem bekerja secara utuh.
php artisan make:test ArtikelTest # tests/Feature/ArtikelTest.php
php artisan make:test HargaTest --unit # tests/Unit/HargaTest.php
php artisan test # atau ./vendor/bin/pest
php artisan test --filter=artikel
php artisan test --parallel # butuh brianium/paratest
php artisan test --coverage --min=80 # butuh Xdebug/PCOV
2. Instalasi Pest
Installer Laravel menawarkan Pest saat membuat proyek. Untuk proyek yang sudah ada:
composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --init
Versi Pest terbaru membutuhkan PHP 8.4+ (cek versi PHP di mesin Anda).
tests/Pest.php menentukan base class dan trait untuk tiap folder:
<?php
pest()->extend(Tests\TestCase::class)
->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
->in('Feature');
Dengan ini, setiap test di tests/Feature otomatis memakai TestCase Laravel dan RefreshDatabase.
3. Pest vs PHPUnit
Test yang sama dalam dua gaya:
<?php
// Pest — tests/Feature/HomeTest.php
it('menampilkan halaman beranda', function () {
$this->get('/')->assertOk();
});
test('harga setelah diskon dihitung benar', function () {
expect(hargaDiskon(100_000, 10))->toBe(90_000);
});
<?php
// PHPUnit — tests/Feature/HomeTest.php
namespace Tests\Feature;
use Tests\TestCase;
class HomeTest extends TestCase
{
public function test_menampilkan_halaman_beranda(): void
{
$this->get('/')->assertOk();
}
}
| Aspek | Pest | PHPUnit |
|---|---|---|
| Bentuk | Fungsi it() / test() | Method class test_* |
| Assertion | expect($x)->toBe() + $this->assert*() | $this->assert*() |
| Data provider | ->with([...]) (dataset) | #[DataProvider] |
| Setup | beforeEach(fn () => ...) | setUp() |
| Kompatibilitas | Menjalankan test PHPUnit juga | — |
4. Database Testing
Konfigurasi
Gunakan database terpisah agar data development tidak terhapus. Opsi tercepat adalah SQLite in-memory di phpunit.xml:
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
Alternatifnya, file .env.testing. Jika produksi memakai MySQL dan query memakai fitur khusus MySQL, lebih aman memakai database MySQL test (misalnya lewat Docker Compose
). Setelah mengubah konfigurasi, jalankan php artisan config:clear.
RefreshDatabase
Trait ini menjalankan migrasi sekali, lalu membungkus setiap test dalam transaksi yang di-rollback. Tiap test mulai dari database bersih tanpa biaya migrasi ulang.
| Trait | Perilaku |
|---|---|
RefreshDatabase | Migrate sekali + transaksi per test (paling umum) |
DatabaseMigrations | Migrate fresh setiap test (lambat) |
DatabaseTruncation | Truncate tabel setiap test (untuk Dusk/browser test) |
Model Factories
Factory membuat data palsu yang realistis untuk model Eloquent :
<?php
// database/factories/ArtikelFactory.php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class ArtikelFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'judul' => fake()->sentence(),
'isi' => fake()->paragraphs(3, true),
'status' => 'draft',
];
}
public function published(): static
{
return $this->state(fn () => ['status' => 'published']);
}
}
$artikel = Artikel::factory()->create(); // simpan ke DB
$draft = Artikel::factory()->make(); // tanpa simpan
$terbit = Artikel::factory()->count(5)->published()->create();
$penulis = User::factory()->has(Artikel::factory()->count(3))->create();
5. Feature Test: HTTP, JSON, dan Auth
Contoh API artikel lengkap:
<?php
// tests/Feature/ArtikelApiTest.php
use App\Models\Artikel;
use App\Models\User;
beforeEach(function () {
$this->user = User::factory()->create();
});
it('menampilkan hanya artikel published', function () {
Artikel::factory()->count(2)->published()->create();
Artikel::factory()->create(); // draft
$this->getJson('/api/artikel')
->assertOk()
->assertJsonCount(2, 'data');
});
it('menolak tamu membuat artikel', function () {
$this->postJson('/api/artikel', ['judul' => 'X'])
->assertUnauthorized();
});
it('membuat artikel untuk user yang login', function () {
$this->actingAs($this->user)
->postJson('/api/artikel', [
'judul' => 'Belajar Pest',
'isi' => 'Pest membuat test Laravel ringkas.',
])
->assertCreated()
->assertJsonPath('data.judul', 'Belajar Pest');
$this->assertDatabaseHas('artikels', [
'judul' => 'Belajar Pest',
'user_id' => $this->user->id,
]);
});
it('memvalidasi input', function (array $payload, string $field) {
$this->actingAs($this->user)
->postJson('/api/artikel', $payload)
->assertUnprocessable()
->assertJsonValidationErrors($field);
})->with([
'judul kosong' => [['isi' => 'abc'], 'judul'],
'isi kosong' => [['judul' => 'abc'], 'isi'],
]);
Assertion yang paling sering dipakai:
| Assertion | Cek |
|---|---|
assertOk(), assertCreated(), assertNoContent() | Status 200 / 201 / 204 |
assertUnauthorized(), assertForbidden(), assertNotFound() | 401 / 403 / 404 |
assertUnprocessable() + assertJsonValidationErrors('x') | Error validasi (422) |
assertJson([...]), assertJsonPath('a.b', v), assertJsonCount(n, 'data') | Isi JSON |
assertRedirect('/login'), assertSessionHasErrors('email') | Form web |
assertDatabaseHas(), assertDatabaseMissing(), assertDatabaseCount() | Isi tabel |
assertSoftDeleted($model) | Soft delete |
6. Fakes: Mail, Queue, Event, Http
Laravel menyediakan fake untuk semua efek samping, sehingga test cepat dan tidak mengirim apa pun ke luar:
<?php
use App\Jobs\ProsesLaporan;
use App\Mail\PesananDiterima;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
it('checkout mengirim email dan men-dispatch job', function () {
Mail::fake();
Queue::fake();
Http::fake([
'api.payment.test/*' => Http::response(['status' => 'paid'], 200),
]);
$user = User::factory()->create();
$this->actingAs($user)->postJson('/api/checkout', ['total' => 150000])
->assertCreated();
Mail::assertSent(PesananDiterima::class, fn ($mail) => $mail->hasTo($user->email));
Queue::assertPushed(ProsesLaporan::class);
Http::assertSentCount(1);
});
Queue::fake() sangat berguna saat menguji kode yang memakai Laravel Queue
: kita cukup memastikan job di-dispatch, lalu menguji handle() job tersebut di test terpisah.
Untuk mencegah request HTTP sungguhan bocor, panggil Http::preventStrayRequests() di beforeEach.
Kesalahan Umum
- Test menghapus database development. Pastikan
DB_CONNECTION/DB_DATABASEdiphpunit.xmlatau.env.testingmenunjuk ke database test. - Config ter-cache. Setelah
php artisan config:cache, variabel diphpunit.xmldiabaikan. Jalankanphp artisan config:clear. - Lupa
RefreshDatabase. Data dari test sebelumnya tertinggal → test lulus/gagal bergantung urutan. - SQLite vs MySQL berbeda perilaku (JSON column, collation, foreign key). Jika memakai fitur spesifik, uji di engine yang sama dengan produksi.
- Fake dipanggil setelah aksi.
Mail::fake()harus sebelum kode yang mengirim email. - Hard-code ID (
/api/artikel/1). Gunakan ID dari model factory. - Terlalu banyak unit test untuk controller. Uji controller lewat HTTP (feature test), bukan dengan memanggil method-nya langsung.
Ringkasan
- Mayoritas test Laravel sebaiknya feature test yang memanggil HTTP.
pest()->extend(TestCase::class)->use(RefreshDatabase::class)->in('Feature')untuk setup sekali.- Factory + state membuat data test ringkas dan realistis.
- Fake Mail/Queue/Http/Event untuk efek samping;
preventStrayRequests()sebagai pengaman. - Untuk alur di browser sungguhan, lanjut ke E2E dengan Playwright .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.