Belajar Livewire - Testing Komponen (Pest/PHPUnit)
Episode 20 of 23

Belajar Livewire - Testing Komponen (Pest/PHPUnit)

Menguji komponen Livewire secara otomatis: Livewire::test untuk memanggil action dan mengeset property, assertion assertSee, assertHasErrors, dan assertDispatched, hingga test CRUD, validasi, dan alur form lengkap.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi yang tumbuh tanpa test adalah bom waktu — refactor yang tampaknya aman bisa menghancurkan fitur yang tidak terpikirkan. Keunggulan arsitektur Livewire adalah komponennya mudah diuji: karena state dan logika hidup di PHP, kalian bisa "memainkan" komponen sepenuhnya di dalam test, tanpa browser.

Di episode ini kita memakai Livewire::test() — API test bawaan yang menginstansiasi komponen, memanggil action, mengeset property, dan mengecek output serta perilaku. Contoh memakai sintaks Pest, yang lebih ringkas, tapi konsepnya sama persis di PHPUnit.

Test Pertama

Install Pest bila belum ada:

Setup Pest
composer require pestphp/pest --dev
php artisan pest:install
tests/Feature/Livewire/CounterTest.php
use Livewire\Livewire;
use App\Livewire\Counter;
 
it('menambah count saat tombol diklik', function () {
    Livewire::test(Counter::class)
        ->call('increment')
        ->assertSee('1');
});
  • Livewire::test(Counter::class) — instansiasi komponen dalam test harness.
  • call('increment') — jalankan action, sama seperti klik dari view.
  • assertSee('1') — pastikan output berisi "1".

Jalankan:

Jalankan test
php artisan test

Assertion yang Paling Sering Dipakai

AssertionKegunaan
assertSee('teks')Output mengandung teks
assertSet('property', 'nilai')Property berisi nilai tertentu
assertHasErrors(['title'])Field punya error validasi
assertDispatched('event')Event terkirim
assertRedirect('/path')Aksi me-redirect
assertViewIs('view.name')View yang dirender

Contoh kombinasi untuk form:

Test validasi dan set
use Livewire\Livewire;
use App\Livewire\CreatePost;
 
it('memvalidasi judul wajib diisi', function () {
    Livewire::test(CreatePost::class)
        ->set('title', '')
        ->call('save')
        ->assertHasErrors(['title' => 'required']);
});
 
it('menyimpan post yang valid', function () {
    Livewire::test(CreatePost::class)
        ->set('title', 'Post pertama')
        ->set('body', 'Isi yang cukup panjang untuk lolos validasi.')
        ->call('save')
        ->assertHasNoErrors()
        ->assertDispatched('post-created')
        ->assertSet('title', '');
 
    $this->assertDatabaseHas('posts', [
        'title' => 'Post pertama',
    ]);
});

Perhatikan alur test-nya meniru user: set field → panggil action → cek efek. set() menggantikan wire:model, call() menggantikan wire:click/wire:submit.

Tip

Saat memakai Form Objects, alur yang sama berjalan: ->set('form.title', '...'). Assertion error juga ikut namespace field, misalnya assertHasErrors(['form.title']). Test tidak membedakan — state form tetap diuji seperti property biasa.

Test Aksi dengan Parameter

Test action dengan parameter
use App\Livewire\PostsIndex;
use App\Models\Post;
 
it('menghapus post yang dipilih', function () {
    $post = Post::factory()->create();
 
    Livewire::test(PostsIndex::class)
        ->call('delete', $post->id)
        ->assertSet('posts', []);
 
    $this->assertDatabaseMissing('posts', ['id' => $post->id]);
});

Test memanggil action dengan argumen persis seperti wire:click="delete({{ $post->id }})".

Test Event dan Komunikasi

Test dispatch dan listener
it('mengirim event post-created', function () {
    Livewire::test(CreatePost::class)
        ->set('title', 'Judul baru')
        ->set('body', 'Isi konten yang valid.')
        ->call('save')
        ->assertDispatched('post-created');
});
 
it('mendengarkan event dan memperbarui statistik', function () {
    Livewire::test(StatsPanel::class)
        ->dispatch('post-created')
        ->assertSee('1');
});

dispatch('event') di sisi test mensimulasikan event yang datang dari komponen lain — tepat untuk menguji listener tanpa perlu memainkan pengirim aslinya.

Test Auth dan Otorisasi

Component yang memakai authorize() butuh user login:

Test otorisasi
it('menolak user non-admin menghapus post', function () {
    $user = User::factory()->create();
    $post = Post::factory()->create(['user_id' => $user->id]);
 
    $this->actingAs($user);
 
    Livewire::test(PostActions::class)
        ->call('delete', $post->id)
        ->assertForbidden();
});
 
it('mengizinkan admin menghapus post', function () {
    $admin = User::factory()->admin()->create();
    $post = Post::factory()->create();
 
    $this->actingAs($admin);
 
    Livewire::test(PostActions::class)
        ->call('delete', $post->id)
        ->assertHasNoErrors();
});

actingAs($user) menyetel user aktif; policy diuji sungguhan — bukan hanya bentuknya.

Test CRUD Lengkap

Test alur CRUD
use App\Livewire\PostEditor;
use App\Models\Post;
 
it('mengedit post yang sudah ada', function () {
    $post = Post::factory()->create([
        'title' => 'Judul lama',
        'body' => 'Konten lama.',
    ]);
 
    Livewire::test(PostEditor::class, ['post' => $post])
        ->assertSet('form.title', 'Judul lama')
        ->set('form.title', 'Judul baru')
        ->call('save')
        ->assertRedirect(route('posts.index'));
 
    $this->assertDatabaseHas('posts', [
        'id' => $post->id,
        'title' => 'Judul baru',
    ]);
});

Perhatikan ['post' => $post] — argumen kedua Livewire::test() dikirim ke mount(), persis seperti <livewire:post-editor :post="$post" /> di view.

Test dengan Alpine / Browser

Livewire::test() tidak menjalankan JavaScript. Untuk interaksi yang murni client-side (dropdown Alpine, transisi), gunakan Laravel Dusk — test browser sungguhan:

Setup Dusk
composer require laravel/dusk --dev
php artisan dusk:install
tests/Browser/ModalTest.php
use Laravel\Dusk\Browser;
 
it('menampilkan modal konfirmasi', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/posts')
            ->click('@delete-button')
            ->waitForText('Yakin ingin menghapus?')
            ->press('Ya, hapus')
            ->waitForText('Post dihapus');
    });
});

Prinsipnya: Livewire::test untuk logika & data; Dusk untuk behavior visual. Keduanya saling melengkapi, tidak menggantikan.

Pitfall Umum

  • Test tanpa actingAs untuk komponen ber-auth: otorisasi gagal tak terduga. Set user sejak awal.
  • assertSee memeriksa teks persis: output Blade bisa mengandung markup tambahan; sesuaikan dengan teks yang benar-benar dirender.
  • Factory belum terpasang: pastikan model punya Model::factory() dan migrasi dijalankan (biasanya otomatis di RefreshDatabase).
  • Hanya menguji "sukses": uji juga jalur gagal (validasi, otorisasi, data kosong).
  • Tidak menjalankan test di CI: jika test tidak pernah berjalan otomatis, ia berhenti menjadi pengaman.

Penutup

Inti yang harus dibawa pulang:

  • Livewire::test(Component::class) menguji komponen tanpa browser; call(), set(), dispatch() memainkannya.
  • Assertion kunci: assertSee, assertSet, assertHasErrors, assertDispatched, assertRedirect, assertForbidden.
  • Test otorisasi dengan actingAs; test Form Objects lewat namespace form.*.
  • CRUD diuji sebagai alur: set → call → cek database.
  • Browser behavior (Alpine, modal) diuji dengan Dusk.

Di episode 21 selanjutnya kita akan membahas performance & production optimization — query efficiency, caching dengan #[Computed], lazy loading, route cache & OPcache, serta cara mengukur payload dan DOM diff sebelum deploy. Sampai jumpa di episode 21!

Belajar Livewire - Testing Komponen (Pest/PHPUnit) | Belajar Livewire