Feature test membebankan seluruh tumpukan aplikasi; HTTP test memberi kita kosakata untuk membaca hasilnya. Alih-alih menebak isi response secara manual, Laravel menyediakan assertion siap pakai untuk status code, isi HTML, struktur JSON, session, cookie, header, sampai file unduhan. Pada chapter ini kita membedah kosakata itu melalui satu suite nyata yang menguji endpoint web dan API dari aplikasi post pada chapter sebelumnya.

Status dan Isi HTML #

Titik awal paling sederhana: get() satu URL, lalu periksa status dan isinya. Assertion assertSee memeriksa keberadaan string di response, assertSeeText membandingkan terhadap teks tampil setelah tag HTML dilucuti, dan assertSeeInOrder memastikan urutan kemunculannya:

php
public function test_daftar_post_tampil_dari_terbaru(): void
{
    Post::factory()->create(['title' => 'Post Terlama']);
    Post::factory()->create(['title' => 'Post Terbaru']);

    $this->get('/posts')
        ->assertOk()
        ->assertSeeInOrder(['Post Terbaru', 'Post Terlama']);
}

Karena controller index memakai Post::latest('id'), post kedua (yang lebih baru) harus muncul lebih dulu — assertSeeInOrder membuktikan urutan itu sekaligus gagal jelas ketika pengurutan berubah.

Endpoint JSON #

Untuk API, pasangan getJson() dan postJson() mengirim request dengan header Accept: application/json sehingga Laravel merespons dalam bentuk JSON, termasuk error validasi 422. Perhatikan endpoint daftar berikut yang juga menyetel custom header:

php
public function test_index_json_mengembalikan_daftar_post(): void
{
    Post::factory()->count(2)->create([
        'title' => 'Belajar HTTP Test',
    ]);

    $response = $this->getJson('/api/posts');

    $response->assertOk()
        ->assertJsonCount(2, 'data')
        ->assertJsonStructure([
            'data' => [
                '*' => ['id', 'title', 'published_at'],
            ],
        ])
        ->assertJsonPath('data.0.title', 'Belajar HTTP Test')
        ->assertHeader('X-Total-Posts', '2');
}

Empat assertion JSON ini saling melengkapi. assertJsonCount(2, 'data') menghitung elemen array pada key data; assertJsonStructure memverifikasi bentuk setiap elemen tanpa peduli nilainya; assertJsonPath mengunci nilai pada path tertentu — di sini post pertama yang terbaru; dan assertHeader memeriksa header custom X-Total-Posts yang disetel controller. Variannya assertJson menerima array yang harus menjadi subset dari response, cocok untuk memastikan field penting tanpa menebak seluruh isi.

Membuat data lewat postJson() juga teruji di suite yang sama:

php
$response = $this->postJson('/api/posts', [
    'user_id' => $penulis->id,
    'title' => 'Post dari API',
    'body' => 'Dibuat lewat postJson di dalam test.',
]);

$response->assertCreated()
    ->assertJson([
        'data' => ['title' => 'Post dari API'],
    ]);

Alur web jarang selesai di satu response. Assertion berikut memeriksa rantai yang menyusul sebuah aksi — ke mana redirect membawa, apa yang disimpan di session, dan cookie apa yang dikirim. Endpoint /preferensi di suite kita memang sengaja membalas dengan cookie:

php
public function test_preferensi_menyimpan_cookie(): void
{
    $this->get('/preferensi')
        ->assertOk()
        ->assertCookie('theme', 'dark');
}

Assertion satu keluarga ini: assertRedirect($url) membaca header Location, assertSessionHas('status', '...') memeriksa flash data yang sudah kita pakai di chapter Feature Tests, assertCookie($name, $value) mendekripsi lalu mencocokkan cookie, dan assertHeader($name, $value) untuk header response apa pun.

Pintasan Status yang Sering Dipakai #

assertStatus(401) valid, tetapi kode berikut lebih terbaca. Laravel menyediakan pintasan untuk status yang paling sering muncul:

AssertionStatusMakna
assertOk()200Request sukses
assertCreated()201Resource baru berhasil dibuat
assertRedirect()302Dialihkan ke URL lain
assertUnauthorized()401Belum terautentikasi
assertForbidden()403Teridentifikasi tapi tak berhak
assertNotFound()404Resource tidak ada
assertUnprocessable()422Validasi gagal

Suite kita memakai dua di antaranya sekaligus untuk menguji halaman admin yang dilindungi middleware auth:

php
public function test_halaman_admin_mewajibkan_login(): void
{
    $this->get('/admin/dashboard')->assertRedirect('/login');
    $this->getJson('/admin/dashboard')->assertUnauthorized();
}

Perilaku middleware berbeda menurut tipe request: tamu web dialihkan ke halaman login (302), sedangkan request JSON menerima badan 401 — dua baris ini membuktikan keduanya.

Assertion View dan Validasi #

Untuk halaman Blade, assertViewIs memastikan view yang tepat dirender dan assertViewHas memeriksa data yang dikirim ke view tersebut:

php
$this->get('/posts')
    ->assertOk()
    ->assertViewIs('posts.index')
    ->assertViewHas('posts', fn ($posts) => $posts->count() === 2);

Sementara itu assertInvalid('title') dan assertJsonValidationErrors('title') memverifikasi pesan validasi untuk request web maupun JSON — yang terakhir mengecek error muncul di struktur JSON response 422.

Mengunduh File #

Response unduhan pun bisa diuji tanpa file nyata. Storage::fake() mengganti storage disk dengan driver memory, dan assertion assertDownload membaca header Content-Disposition:

php
public function test_unduhan_laporan_mengirim_file(): void
{
    Storage::fake();
    Storage::put('report.txt', 'Isi laporan penjualan.');

    $this->get('/download/report')
        ->assertOk()
        ->assertDownload('laporan.txt');
}

Seluruh Suite Berjalan #

Kesembilan test di atas hidup dalam satu berkas tests/Feature/PostHttpTest.php:

bash
php artisan test tests/Feature/PostHttpTest.php

Output yang Dihasilkan:

text
  PASS  Tests\Feature\PostHttpTest
  ✓  index json mengembalikan daftar post                                0.16s
  ✓  store json membuat post baru                                        0.01s
  ✓  store json menolak data tanpa judul                                  0.01s
  ✓  post tidak ada mengembalikan 404                                    0.01s
  ✓  halaman admin mewajibkan login                                      0.01s
  ✓  halaman daftar menggunakan view dan data yang tepat                 0.01s
  ✓  daftar post tampil dari terbaru                                      0.01s
  ✓  unduhan laporan mengirim file                                       0.01s
  ✓  preferensi menyimpan cookie                                          0.01s

  Tests:    9 passed (35 assertions)
  Duration: 0.32s

Kosakata ini lengkap, tetapi ada satu aktor yang belum kita periksa secara ketat: database itu sendiri. Bagaimana test yang satu tidak mengotori test yang lain? Chapter Database Testing menjawabnya dengan membedah RefreshDatabase dan strategi isolasinya. Daftar assertion lengkap tersedia di dokumentasi HTTP Tests .