Pengujian HTTP

Helper get, post, dan json beserta asersi respons yang lazim.

Daftar isi

Kasus tes di Laravel dapat memanggil aplikasi melalui HTTP tanpa menjalankan server, yaitu melalui helper get(), post(), dan json() yang tersedia pada Tests\TestCase. Topik ini membahas cara kerja ketiga helper tersebut beserta respons TestResponse yang dikembalikan, dilanjutkan dengan asersi respons yang lazim digunakan untuk memeriksa status, isi HTML, struktur JSON, dan kegagalan validasi. Seluruh contoh dijalankan pada aplikasi catatan dengan rute contoh yang didefinisikan langsung di dalam kelas tesnya, sehingga setiap kasus dapat direproduksi tanpa menyunting berkas rute aplikasi.

Helper get untuk membaca respons#

Helper get() menyusun permintaan GET, menyalurkannya ke router aplikasi dalam proses yang sama, lalu mengembalikan objek TestResponse, yaitu pembungkus respons beserta metode asersinya. Karena permintaannya diselesaikan di dalam proses tes, tidak ada jaringan, tidak ada port yang dibuka, dan tidak ada php artisan serve yang perlu dijalankan. Pada proyek nyata, rute yang diuji biasanya terdaftar di routes/web.php atau routes/api.php, sedangkan contoh berikut mendaftarkan rutenya pada setUp() agar kasusnya berdiri sendiri.

Kelas berikut memuat seluruh kasus yang digunakan sepanjang topik ini. Dua kasus pertama dijalankan terlebih dahulu untuk menunjukkan penggunaan get() pada respons HTML dan respons JSON.

bash
# menyiapkan kelas tes HTTP lalu menjalankan dua kasus get
cd ~/aplikasi-catatan
cat > tests/Feature/RuteUjiHttpTest.php <<'PHP'
<?php

namespace Tests\Feature;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Tests\TestCase;

class RuteUjiHttpTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        Route::get('/uji/halo', function () {
            return '<html><body><h1>Halo, Laravel!</h1></body></html>';
        });

        Route::get('/uji/catatan', function () {
            return ['judul' => 'Laporan harian', 'kategori' => 'Kerja'];
        });

        Route::post('/uji/catatan', function (Request $request) {
            $data = $request->validate(['judul' => ['required', 'string', 'max:255']]);

            return response()->json(['judul' => $data['judul']], 201);
        });

        Route::post('/uji/pindah', function () {
            return redirect('/uji/catatan');
        });
    }

    public function test_get_mengembalikan_html(): void
    {
        $respons = $this->get('/uji/halo');

        $respons->assertOk();
        $respons->assertSee('Laravel');
    }

    public function test_get_mengembalikan_data_json(): void
    {
        $respons = $this->get('/uji/catatan');

        $respons->assertOk();
        $respons->assertJsonPath('judul', 'Laporan harian');
    }

    public function test_post_mengalihkan_ke_tujuan_baru(): void
    {
        $respons = $this->post('/uji/pindah');

        $respons->assertRedirect('/uji/catatan');
    }

    public function test_post_json_menyimpan_judul_baru(): void
    {
        $respons = $this->postJson('/uji/catatan', ['judul' => 'Catatan baru']);

        $respons->assertCreated();
        $respons->assertJsonPath('judul', 'Catatan baru');
    }

    public function test_post_json_tanpa_judul_ditolak_validasi(): void
    {
        $respons = $this->postJson('/uji/catatan', []);

        $respons->assertStatus(422);
        $respons->assertJsonValidationErrors(['judul']);
    }
}
PHP
php artisan test --filter="RuteUjiHttpTest::test_get" | grep -E "PASS|Tests:"
text Keluaran
   PASS  Tests\Feature\RuteUjiHttpTest
  Tests:    2 passed (4 assertions)

Kedua kasus lulus dengan empat asersi. Kasus pertama memanggil /uji/halo dan memeriksa dua hal, yaitu status 200 melalui assertOk() dan kemunculan teks Laravel pada HTML-nya melalui assertSee(). Kasus kedua memanggil /uji/catatan yang mengembalikan array, dan Laravel merender array tersebut sebagai respons JSON secara otomatis, sehingga assertJsonPath('judul', 'Laporan harian') dapat membaca kunci judul dari isinya.

Helper post dan json untuk mengirim data#

Helper post() mengirim data formulir pada isi permintaan, sedangkan helper json() mengirim data dengan header Content-Type: application/json beserta Accept: application/json. Bentuk singkatnya tersedia untuk dua method yang paling sering digunakan, yaitu postJson() untuk POST dan getJson() untuk GET, sedangkan method lain seperti PUT dan DELETE menggunakan json() langsung dengan method tersebut sebagai argumen pertama. Pemanggilan $this->json('POST', '/uji/catatan', $data) menghasilkan permintaan yang sama dengan $this->postJson('/uji/catatan', $data).

Dua kasus berikutnya menunjukkan post() pada rute yang menjawab pengalihan dan postJson() pada rute yang menyimpan data.

bash
# menjalankan kasus post dengan pengalihan dan postJson dengan penyimpanan
cd ~/aplikasi-catatan
php artisan test --filter="test_post_mengalihkan_ke_tujuan_baru|test_post_json_menyimpan_judul_baru" | grep -E "PASS|Tests:"
text Keluaran
   PASS  Tests\Feature\RuteUjiHttpTest
  Tests:    2 passed (4 assertions)

assertRedirect('/uji/catatan') memeriksa header Location pada respons pengalihan, sedangkan assertCreated() memeriksa status 201 dari rute yang menjawab response()->json([...], 201). Nilai judul pada respons JSON kembali diperiksa dengan assertJsonPath(), sehingga kasus ini membuktikan bukan hanya statusnya, melainkan juga isi yang dikirim kembali oleh rutenya.

Asersi respons yang lazim#

Tabel berikut merangkum asersi yang paling sering digunakan pada pengujian HTTP beserta hal yang diperiksanya.

AsersiYang diperiksa
assertOk()Status respons bernilai 200
assertCreated()Status respons bernilai 201
assertNotFound()Status respons bernilai 404
assertStatus(422)Status respons bernilai persis seperti argumennya
assertRedirect('/uji/catatan')Header Location menunjuk tujuan yang diberikan
assertSee('Laravel')Teks muncul pada respons HTML, dengan escape otomatis
assertJsonPath('judul', 'Laporan harian')Nilai satu kunci JSON sama dengan nilai yang diberikan
assertJson(['judul' => 'Laporan harian'])Struktur JSON memuat bagian yang sama seperti argumennya
assertExactJson(['judul' => 'Laporan harian'])Struktur JSON sama persis dengan argumennya
assertJsonValidationErrors(['judul'])Respons validasi JSON memuat error untuk kunci tersebut

Beberapa asersi pelengkap tersedia untuk kebutuhan yang lebih spesifik. assertSeeInOrder() memeriksa kemunculan beberapa teks pada HTML dengan urutan yang sama seperti argumennya, sedangkan assertHeader() memeriksa keberadaan dan nilai satu header respons. Asersi lain seperti assertSessionHas() dan assertInvalid() bekerja pada session, sehingga membutuhkan rute dengan middleware web, karena hanya grup tersebut yang membawa middleware session ke dalam permintaannya.

Kasus terakhir menunjukkan pemeriksaan kegagalan validasi. Rute /uji/catatan memanggil validate() dengan aturan required, sehingga permintaan tanpa judul memicu ValidationException yang dirender sebagai respons 422.

bash
# menjalankan kasus validasi tanpa judul
cd ~/aplikasi-catatan
php artisan test --filter="test_post_json_tanpa_judul_ditolak_validasi" | grep -E "PASS|Tests:"
text Keluaran
   PASS  Tests\Feature\RuteUjiHttpTest
  Tests:    1 passed (3 assertions)

Status 422 diperiksa dengan assertStatus(), sedangkan assertJsonValidationErrors(['judul']) memastikan daftar error pada respons JSON memuat kunci judul. Karena permintaan dikirim melalui postJson(), pengecualian validasinya dirender dalam bentuk JSON dan bukan pengalihan kembali ke formulir, sehingga asersi JSON seperti ini yang cocok untuk rute API.

Kesalahan umum#

Kesalahan pertama adalah mengira helper ini mengirim permintaan melalui jaringan. Seluruh permintaan diselesaikan router di dalam proses tes, sehingga server pengembangan tidak diperlukan dan pengujian tetap berjalan meskipun php artisan serve tidak dijalankan. Konsekuensinya, URL yang tidak terdaftar sebagai rute dijawab dengan 404 dari router yang sama, dan asersi seperti assertNotFound() dapat membuktikannya.

Kesalahan kedua adalah mencari potongan HTML mentah dengan assertSee(). Metode ini melakukan escape pada argumennya secara otomatis, sehingga pencarian seperti assertSee('<b>halo</b>') mencari versi yang sudah di-escape dan tidak pernah cocok dengan isi respons. Untuk teks yang ingin dicocokkan apa adanya, berikan argumen kedua false, misalnya assertSee('<b>halo</b>', false).

Kesalahan ketiga adalah menganggap assertJson() membandingkan seluruh isi respons. Asersi ini memeriksa bahwa argumennya merupakan bagian dari respons, sehingga kunci tambahan apa pun tidak menggagalkannya. Ketika struktur yang diharapkan harus persis sama, gunakan assertExactJson(), atau periksa kunci tertentu satu per satu dengan assertJsonPath().

Ringkasan#

  • Helper get(), post(), dan json() menyusun permintaan di dalam proses tes tanpa server, lalu mengembalikan objek TestResponse yang membawa asersi respons.
  • Status diperiksa dengan assertOk(), assertCreated(), assertNotFound(), dan assertStatus(), pengalihan dengan assertRedirect(), teks HTML dengan assertSee(), dan isi JSON dengan assertJsonPath(), assertJson(), atau assertExactJson().
  • Kegagalan validasi pada permintaan JSON dirender sebagai respons 422 dan diperiksa dengan assertJsonValidationErrors(), sedangkan postJson() dan getJson() merupakan bentuk singkat dari json() dengan method yang sesuai.

Referensi#