Testing Laravel 11 untuk Pemula: Pest, PHPUnit & Contoh

Anda menambah fitur diskon di aplikasi kasir, mengetesnya di browser, semuanya beres. Dua hari kemudian pelanggan melapor bahwa total belanja tanpa diskon ikut salah hitung. Fitur...

Testing Laravel 11 untuk Pemula: Pest, PHPUnit & Contoh

Anda menambah fitur diskon di aplikasi kasir, mengetesnya di browser, semuanya beres. Dua hari kemudian pelanggan melapor bahwa total belanja tanpa diskon ikut salah hitung. Fitur lama rusak diam-diam karena tidak ada yang mengeceknya ulang. Inilah masalah yang diselesaikan testing otomatis.

Laravel sudah membawa semua perkakas testing sejak instalasi, jadi tidak ada alasan menundanya. Artikel ini membahas cara menulis test pertama, menguji database dengan aman, memalsukan email dan API eksternal, serta error yang paling sering membuat pemula menyerah.

Kenapa Test Layak Ditulis

  • Mencegah regresi. Satu perintah memeriksa ulang puluhan skenario dalam hitungan detik.
  • Dokumentasi yang selalu benar. Test menjelaskan perilaku yang diharapkan, dan langsung gagal jika perilaku itu berubah.
  • Berani merapikan kode. Refactor tidak lagi menakutkan karena ada jaring pengaman.

Biayanya nyata: menulis test menambah waktu di awal. Karena itu, mulailah dari fitur yang paling mahal jika rusak, misalnya login, checkout, dan perhitungan uang, bukan dari halaman "Tentang Kami".

Feature Test vs Unit Test

AspekFeature testUnit test
Foldertests/Featuretests/Unit
Yang diujiRequest HTTP sampai respons dan isi databaseSatu class atau method secara terisolasi
Akses frameworkPenuh (routing, database, session)Tidak ada; murni PHP
KecepatanLebih lambatSangat cepat
Cocok untukAlur pengguna: simpan produk, login, checkoutLogika murni: kalkulator diskon, format nomor faktur

Untuk pemula, feature test memberi manfaat paling besar karena menguji hal yang benar-benar dialami pengguna.

Langkah 1: Siapkan Database Khusus Test

Jangan pernah menjalankan test terhadap database yang berisi data asli. Buka phpunit.xml dan aktifkan dua baris yang secara default masih berupa komentar:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

SQLite di memori dibuat ulang setiap kali test berjalan, cepat, dan tidak menyentuh MySQL Anda. Jika aplikasi memakai fitur khusus MySQL (misalnya fungsi FIELD() atau fulltext), buat database MySQL terpisah bernama misalnya toko_testing dan arahkan ke sana.

Langkah 2: Buat File Test

php artisan make:test ProductTest          # feature test
php artisan make:test DiscountTest --unit  # unit test

Installer Laravel 11 menawarkan pilihan Pest atau PHPUnit saat membuat project. Keduanya memakai mesin yang sama; Pest hanya menyediakan sintaks yang lebih ringkas. Contoh di bawah memakai PHPUnit karena gayanya lebih dikenal, diikuti versi Pest untuk perbandingan.

Langkah 3: Test Pertama

namespace Tests\Feature;

use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ProductTest extends TestCase
{
    use RefreshDatabase;

    public function test_tamu_dialihkan_ke_login(): void
    {
        $this->get('/products')->assertRedirect('/login');
    }

    public function test_user_bisa_menambah_produk(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->post('/products', [
            'name'  => 'Kopi Susu',
            'price' => 15000,
            'stock' => 10,
        ]);

        $response->assertRedirect('/products');
        $this->assertDatabaseHas('products', ['name' => 'Kopi Susu', 'price' => 15000]);
    }

    public function test_harga_negatif_ditolak(): void
    {
        $user = User::factory()->create();

        $this->actingAs($user)
            ->post('/products', ['name' => 'Teh', 'price' => -1, 'stock' => 1])
            ->assertSessionHasErrors('price');

        $this->assertDatabaseCount('products', 0);
    }
}

Ada tiga pola penting di sini. RefreshDatabase menjalankan migrasi dan membersihkan data di setiap test, sehingga urutan test tidak saling memengaruhi. actingAs() login sebagai user tanpa melewati form. Dan test ketiga menguji jalur gagal, bagian yang paling sering terlupa padahal paling sering dimanfaatkan input nakal.

Versi Pest untuk test kedua terlihat seperti ini:

use App\Models\User;

uses(Illuminate\Foundation\Testing\RefreshDatabase::class);

test('user bisa menambah produk', function () {
    $user = User::factory()->create();

    $this->actingAs($user)
        ->post('/products', ['name' => 'Kopi Susu', 'price' => 15000, 'stock' => 10])
        ->assertRedirect('/products');

    expect(\App\Models\Product::count())->toBe(1);
});

Pola Arrange, Act, Assert

Perhatikan bahwa setiap test di atas punya tiga bagian. Arrange: siapkan kondisi, misalnya membuat user lewat factory. Act: lakukan satu aksi, misalnya mengirim POST. Assert: periksa hasilnya. Pisahkan ketiganya dengan baris kosong. Jika satu test mulai berisi beberapa aksi berbeda, itu tanda test tersebut sebaiknya dipecah menjadi dua agar pesan gagalnya jelas menunjuk penyebab.

Langkah 4: Unit Test untuk Logika Murni

Misalkan perhitungan diskon dipisah ke class App\Support\Discount dengan method statis apply(int $total, int $percent): int. Unit test-nya tidak butuh database sama sekali:

namespace Tests\Unit;

use App\Support\Discount;
use PHPUnit\Framework\TestCase;

class DiscountTest extends TestCase
{
    public function test_diskon_sepuluh_persen(): void
    {
        $this->assertSame(90000, Discount::apply(100000, 10));
    }

    public function test_diskon_tidak_membuat_total_negatif(): void
    {
        $this->assertSame(0, Discount::apply(50000, 150));
    }
}

Perhatikan bahwa unit test mewarisi PHPUnit\Framework\TestCase, bukan Tests\TestCase, sehingga framework tidak di-boot dan test berjalan sangat cepat. Memisahkan logika seperti ini juga membuat kode lebih mudah dipahami.

Langkah 5: Palsukan Email, Antrean, dan API Luar

Test tidak boleh mengirim email sungguhan atau memanggil payment gateway asli. Laravel menyediakan fake untuk itu:

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;

Mail::fake();
Http::fake([
    'api.contoh-ongkir.test/*' => Http::response(['cost' => 12000], 200),
]);

// ... jalankan request checkout ...

Mail::assertSent(\App\Mail\InvoiceMail::class);
Http::assertSentCount(1);

Nama domain dan class mail di atas hanyalah contoh. Polanya sama untuk Queue::fake(), Notification::fake(), dan Storage::fake().

Menjalankan Test

php artisan test                       # semua test
php artisan test --filter=ProductTest  # satu file atau nama test
php artisan test --parallel            # paralel, perlu paket brianium/paratest

Menentukan Apa yang Diuji Lebih Dulu

Pada project yang sudah berjalan tanpa test, menulis test untuk semuanya sekaligus tidak realistis. Gunakan urutan prioritas berikut:

  1. Uang. Perhitungan total, diskon, pajak, dan saldo. Kesalahan di sini langsung merugikan.
  2. Akses. Halaman admin tidak bisa dibuka user biasa, data satu pelanggan tidak terlihat oleh pelanggan lain.
  3. Alur utama. Daftar, login, membuat transaksi, mencetak faktur.
  4. Bug yang pernah terjadi. Setiap bug yang diperbaiki mendapat satu test agar tidak muncul lagi.
  5. Sisanya ditambah bertahap setiap kali Anda menyentuh fitur tersebut.

Jangan mengejar angka cakupan (coverage) seratus persen. Sepuluh test yang menjaga alur penting jauh lebih berharga daripada ratusan test yang hanya memeriksa halaman statis.

Menjalankan Test Otomatis di Setiap Push

Test paling berguna ketika berjalan tanpa perlu diingat. Jika repositori Anda di GitHub, buat file .github/workflows/tests.yml:

name: tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - run: composer install --no-interaction --prefer-dist
      - run: cp .env.example .env && php artisan key:generate
      - run: php artisan test

Setiap push dan pull request kini otomatis dites, dan tanda silang merah muncul sebelum kode bermasalah sampai ke server.

Error Umum dan Perbaikannya

ErrorPenyebabPerbaikan
no such table: productsLupa RefreshDatabaseTambahkan trait di class test
Call to undefined method factory()Model belum memakai HasFactoryTambahkan trait di model
Vite manifest not foundAsset frontend belum di-buildPanggil $this->withoutVite() atau jalankan npm run build
Data asli hilang setelah testTest memakai database utamaAtur koneksi testing di phpunit.xml
Test lolos sendiri, gagal bersamaTest saling bergantung pada dataBuat data di dalam masing-masing test

Checklist Kebiasaan Testing

  • Setiap bug yang ditemukan dibuatkan test sebelum diperbaiki.
  • Setiap fitur punya minimal satu test jalur sukses dan satu jalur gagal.
  • Test tidak pernah menyentuh database produksi atau layanan berbayar.
  • php artisan test dijalankan sebelum commit dan sebelum deploy.
  • Nama test menjelaskan perilaku, bukan nama method.

Jika Anda baru belajar membangun fitur, mulailah dari CRUD Laravel 11 dari nol, lalu tambahkan test untuk setiap aksinya. Test juga sangat membantu saat mengkustomisasi source code tanpa merusak, dan sebaiknya menjadi langkah wajib sebelum Anda deploy aplikasi Laravel.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Testing Laravel Programming & Laravel
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →