Mengamankan kualitas dengan automated testing: feature & unit tests, sintaks Pest yang lebih ekspresif, database testing dengan RefreshDatabase, dan browser E2E dengan Laravel Dusk.

Kita sudah membangun aplikasi selama 20 episode — sekarang saatnya memastikan semuanya tetap bekerja saat aplikasi tumbuh. Automated testing adalah jaring pengaman yang membuat perubahan kode tidak menakutkan: satu tombol php artisan test memberi tahu kalian apakah sesuatu rusak.
Di episode ini kita pelajari dua lapis testing: unit/feature test (logika & HTTP) dan browser test dengan Dusk (alur nyata di browser). Laravel 13 secara default memakai Pest sebagai test framework.
| Jenis | Fokus | Kecepatan |
|---|---|---|
| Unit test | Satu class/method, tanpa framework penuh | Sangat cepat |
| Feature test | HTTP request end-to-end, controller + DB | Sedang |
Laravel 13 menyediakan keduanya via php artisan make:test:
php artisan make:test PostTest<?php
use App\Models\Post;
use App\Models\User;
test('published posts are visible on index', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/posts')
->assertOk()
->assertSee('Post Pertama');
});$this->actingAs($user) mensimulasikan login; assertOk()/assertSee() memverifikasi response. Feature test menjalankan seluruh request lifecycle — route, middleware, controller, view — jadi sangat dekat dengan perilaku nyata.
Pest adalah layer di atas PHPUnit dengan sintaks test() dan describe():
describe('posts', function () {
test('guests are redirected to login', function () {
$this->get('/posts/create')->assertRedirect('/login');
});
test('authenticated user can create post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/posts', ['title' => 'Halo', 'body' => 'Isi'])
->assertRedirect('/posts');
$this->assertDatabaseHas('posts', ['title' => 'Halo']);
});
});assertDatabaseHas memverifikasi data benar-benar masuk database — kombinasi yang sangat kuat untuk menguji alur create. Fungsi helper juga bisa dibuat sendiri dan dipakai antar test.
Note
Pest dan PHPUnit bukan dua dunia terpisah: Pest berjalan di atas PHPUnit, jadi kalian bisa memakai keduanya dalam satu proyek. Jika proyek dibuat tanpa Pest, konversi ke Pest adalah composer require pestphp/pest + php artisan pest:install. Laravel 13 default ke Pest saat laravel new.
Test yang menyentuh database harus terisolasi. Trait RefreshDatabase menangani ini: setiap test memulai dari database yang bersih, tanpa mengganggu data development kalian.
use Illuminate\Foundation\Testing\RefreshDatabase;
test('post slug is unique', function () {
Post::factory()->create(['slug' => 'duplicate']);
Post::factory()->create(['slug' => 'duplicate']);
})->expectsException(QueryException::class);Di balik layar, RefreshDatabase menangani transaction rollback per-test sehingga cepat, dan menjalankan migrasi sekali. Pastikan konfigurasi test database terpisah dari database development — di .env.testing atau phpunit.xml:
<env name="DB_DATABASE" value=":memory:"/>SQLite :memory: adalah pilihan paling cepat untuk test lokal.
use App\Models\User;
use Laravel\Sanctum\Sanctum;
test('api returns paginated posts', function () {
Sanctum::actingAs(User::factory()->create());
$this->getJson('/api/posts')
->assertOk()
->assertJsonStructure(['data', 'links', 'meta']);
});test('non-owner cannot update post', function () {
$owner = User::factory()->create();
$post = Post::factory()->for($owner)->create();
$intruder = User::factory()->create();
$this->actingAs($intruder)
->put("/posts/{$post->id}", ['title' => 'Hack'])
->assertForbidden();
});use Illuminate\Support\Facades\Queue;
Queue::fake();
Post::factory()->create();
Queue::assertPushed(SendWelcomeEmail::class);Queue::fake() mencegah job benar-benar dieksekusi dan memungkinkan assertion apakah job di-dispatch — pola penting untuk menguji alur tanpa efek samping nyata.
Feature test tidak menjalankan JavaScript. Dusk menguji alur nyata di browser — login, klik, form, dan interaksi JS:
composer require --dev laravel/dusk
php artisan dusk:installphp artisan dusk<?php
namespace Tests\Browser;
use App\Models\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
test('user can login', function () {
$user = User::factory()->create([
'email' => 'test@example.com',
'password' => bcrypt('password123'),
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password123')
->press('Login')
->assertPathIs('/dashboard')
->assertSee('Dashboard');
});
});Dusk mengendalikan browser sungguhan (Chrome/Chromium via ChromeDriver). Ini lapisan terakhir pengujian: jika alur auth dan CRUD lolos di browser sungguhan, keyakinan terhadap aplikasi sangat tinggi.
Inti yang harus dibawa pulang:
php artisan test menjalankan seluruh suite.assertDatabaseHas memverifikasi data.RefreshDatabase mengisolasi test database; gunakan SQLite :memory: di local.Queue::fake() + assertPushed.Di episode 22 selanjutnya kita akan membahas deployment — Forge untuk VPS, Vapor untuk serverless AWS, Docker (PHP-FPM + Nginx + MySQL/Redis), optimasi production config:cache/route:cache/opcache, dan ekosistem Nova/Cashier/Envoyer. Sampai jumpa di episode 22!