Test yang baik harus berjalan di lingkungan yang aman: tidak menyentuh database produksi, tidak mengirim email asli, dan tidak mengotori data pengguna. Laravel menyiapkan pemisahan ini sejak instalasi pertama lewat berkas phpunit.xml dan base class Tests\TestCase. Pada chapter ini kita bedah keduanya, lalu membedakan dua cara menjalankan suite — php artisan test dan vendor/bin/phpunit — beserta strategi reset database antar test.

Membedah phpunit.xml #

PHPUnit membaca konfigurasinya dari phpunit.xml di root proyek. Inilah berkas bawaan Laravel 13:

xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory>tests/Feature</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory>app</directory>
        </include>
    </source>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="APP_MAINTENANCE_DRIVER" value="file"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="BROADCAST_CONNECTION" value="null"/>
        <env name="CACHE_STORE" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="DB_URL" value=""/>
        <env name="MAIL_MAILER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="PULSE_ENABLED" value="false"/>
        <env name="TELESCOPE_ENABLED" value="false"/>
        <env name="NIGHTWATCH_ENABLED" value="false"/>
    </php>
</phpunit>

Blok <testsuites> memberitahu PHPUnit dua direktori test: tests/Unit dan tests/Feature. Blok <php> adalah jantungnya: setiap <env> menyuntikkan variable environment yang menimpa nilai .env selama test berjalan. Nilai-nilainya sengaja dipilih agar test cepat dan tanpa efek samping — cache, session, dan mail berjalan di array dalam memori, sementara queue berjalan sinkron (job langsung dieksekusi, tanpa worker), dan panel observability seperti Telescope dimatikan.

Perhatikan dua baris database: DB_CONNECTION=sqlite dan DB_DATABASE=:memory:. Artinya seluruh test memakai SQLite in-memory — database baru yang hanya hidup selama proses test, lenyap begitu proses selesai. Tidak ada risiko menghapus data development, dan tidak perlu server database eksternal. Sementara itu BCRYPT_ROUNDS=4 memangkas biaya hashing password dari default 12 putaran menjadi 4 — cukup aman untuk test karena tujuannya hanya verifikasi kecocokan, bukan ketahanan brute force.

Environment Test Terpisah: .env.testing #

Selain phpunit.xml, Laravel juga mendukung berkas .env.testing. Ketika APP_ENV=testing, Laravel memuat .env.testing sebagai pengganti .env — karena itu berkas ini idealnya salinan lengkap .env dengan nilai yang disesuaikan. Variable yang juga ada di phpunit.xml tetap dimenangkan phpunit.xml karena disuntik lebih dulu sebagai environment variable sungguhan.

Kita bisa membuktikan kedua lapisan ini lewat unit test kecil yang membaca konfigurasi aplikasi:

php
<?php

namespace Tests\Unit;

use Tests\TestCase;

class EnvironmentTest extends TestCase
{
    public function test_environment_testing_terpasang(): void
    {
        $this->assertSame('testing', config('app.env'));
        $this->assertSame('Belajar Laravel Testing', config('app.name'));
        $this->assertSame('sqlite', config('database.default'));
        $this->assertSame(':memory:', config('database.connections.sqlite.database'));
    }
}

Pada sandbox, .env.testing berisi APP_NAME="Belajar Laravel Testing" dan APP_ENV=local — namun assertion pertama tetap lulus dengan testing karena phpunit.xml menang. Assertion kedua lulus dari .env.testing, dan dua assertion terakhir membuktikan database in-memory aktif. Hasil eksekusinya:

bash
php artisan test tests/Unit/EnvironmentTest.php

Output yang Dihasilkan:

text
  PASS  Tests\Unit\EnvironmentTest
  ✓  environment testing terpasang                                       0.08s

  Tests:    1 passed (4 assertions)
  Duration: 0.16s

Peran Tests\TestCase #

Tests\TestCase meng-extend Illuminate\Foundation\Testing\TestCase yang mem-boot aplikasi Laravel utuh — container, configuration, service provider — sebelum setiap test, lalu membongkarnya setelah test selesai. Boot inilah yang mengaktifkan fasilitas $this->get(), $this->actingAs(), dan puluhan assertion lain.

Berbeda dengan Laravel versi lama, skeleton Laravel 13 tidak lagi menyediakan tests/Unit/TestCase.php. Unit test bawaan langsung meng-extend PHPUnit\Framework\TestCase sehingga berjalan tanpa framework. Test EnvironmentTest di atas menunjukkan jalur keluarnya: jika sebuah unit test memang butuh aplikasi (misalnya membaca config()), cukup ubah parent class menjadi Tests\TestCase — dengan konsekuensi test tersebut menjadi sedikit lebih lambat.

Transaksi vs Migrasi per Test #

Test yang menyentuh database butuh strategi reset. Trait RefreshDatabase — yang akan kita pakai intensif mulai chapter Feature Tests — menjalankan migrate:fresh hanya sekali per proses test, lalu membungkus setiap test dalam database transaction yang di-rollback otomatis. Pada SQLite :memory:, koneksi PDO-nya bahkan di-cache statis agar schema yang sudah dibuat tetap hidup antar test. Detail mekanisme ini kita bedah tuntas di chapter Database Testing.

artisan test vs vendor/bin/phpunit #

Kedua perintah menjalankan PHPUnit yang sama, tetapi pengalamannya berbeda:

bash
php artisan test

Output yang Dihasilkan:

text
  PASS  Tests\Feature\PostManagementTest
  ✓  post baru tersimpan dan muncul di daftar                            0.17s
  ✓  post wajib memiliki judul                                           0.01s
  ✓  detail post menampilkan isi lengkap                                 0.01s

  Tests:    3 passed (13 assertions)
  Duration: 0.27s

Perintah artisan mencetak setiap test beserta durasinya dalam warna, lalu ringkasan di akhir. Bandingkan dengan pemanggilan PHPUnit langsung:

bash
vendor/bin/phpunit

Output yang Dihasilkan:

text
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.4.8
Configuration: /home/z/my-project/work/sandboxes/w12-c/phpunit.xml

...............................                                       31 / 31 (100%)

Time: 00:00.514, Memory: 56.50 MB

OK (31 tests, 84 assertions)

Output vendor/bin/phpunit berupa satu titik per test — ringkas untuk CI, tetapi lebih sulit dibaca saat menelusuri test lambat. Sebagai praktik sehari-hari gunakan php artisan test; opsi PHPUnit seperti --filter tetap bisa ditempelkan, misalnya php artisan test --filter=PriceCalculatorTest.

Dengan konfigurasi yang kini kita pahami, saatnya menulis test sungguhan. Kita mulai dari lapisan dasar piramida: unit test untuk class murni tanpa framework. Rujukan lengkapnya ada di dokumentasi Testing Laravel .