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

FolderBoot aplikasi?Untuk
tests/Unit❌ TidakLogika murni: helper, value object, kalkulasi
tests/Feature✅ YaHTTP 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();
    }
}
AspekPestPHPUnit
BentukFungsi it() / test()Method class test_*
Assertionexpect($x)->toBe() + $this->assert*()$this->assert*()
Data provider->with([...]) (dataset)#[DataProvider]
SetupbeforeEach(fn () => ...)setUp()
KompatibilitasMenjalankan 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.

TraitPerilaku
RefreshDatabaseMigrate sekali + transaksi per test (paling umum)
DatabaseMigrationsMigrate fresh setiap test (lambat)
DatabaseTruncationTruncate 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:

AssertionCek
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

  1. Test menghapus database development. Pastikan DB_CONNECTION/DB_DATABASE di phpunit.xml atau .env.testing menunjuk ke database test.
  2. Config ter-cache. Setelah php artisan config:cache, variabel di phpunit.xml diabaikan. Jalankan php artisan config:clear.
  3. Lupa RefreshDatabase. Data dari test sebelumnya tertinggal → test lulus/gagal bergantung urutan.
  4. SQLite vs MySQL berbeda perilaku (JSON column, collation, foreign key). Jika memakai fitur spesifik, uji di engine yang sama dengan produksi.
  5. Fake dipanggil setelah aksi. Mail::fake() harus sebelum kode yang mengirim email.
  6. Hard-code ID (/api/artikel/1). Gunakan ID dari model factory.
  7. 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.

Comments