Kode event-driven punya dua sisi yang perlu diuji dengan cara berbeda. Sisi pengiriman: apakah aksi bisnis yang benar men-trigger event dengan payload yang benar? Sisi reaksi: apakah listener benar-benar melakukan pekerjaannya? Event::fake() menjawab sisi pertama — fake menggantikan dispatcher sehingga event tercatat tanpa menjalankan satu listener pun, cepat dan tanpa efek samping. Untuk sisi kedua kita melepas fake dan membiarkan listener sungguhan berjalan. Chapter penutup section ini menyusun test suite yang mencakup keduanya, selaras dengan pola pada dokumentasi Mocking .

Event::fake dan Assertion Dasar #

Event::fake() menukar dispatcher dengan EventFake yang mencatat setiap dispatch. Sebelum masuk ke test pertama, satu persiapan kecil: seluruh test di chapter ini memakai kolektor $this->terlog — array yang diisi lewat Log::listen() di setUp() sehingga kita bisa memeriksa log mana yang benar-benar tertulis oleh listener:

php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Log;

class EventChapterTest extends TestCase
{
    use RefreshDatabase;

    private array $terlog = [];

    protected function setUp(): void
    {
        parent::setUp();

        Log::listen(function ($pesan) {
            $this->terlog[] = $pesan->message;
        });
    }
}

Dengan kolektor itu siap, berikut test pertama — kita kirim OrderPlaced lalu memastikannya tercatat:

php
public function test_fake_merekam_dispatch_tanpa_mengeksekusi_listener(): void
{
    Event::fake();

    OrderPlaced::dispatch('INV-1001', '[email protected]', 250000);

    Event::assertDispatched(OrderPlaced::class);
    Event::assertNotDispatched(PaymentSucceeded::class);

    fwrite(STDERR, 'listener_sinkron_jalan: '
        .var_export(in_array('konfirmasi-dikirim', $this->terlog), true)."\n");
    fwrite(STDERR, 'job_masuk_antrean: '.DB::table('jobs')->count()."\n");
}

Output yang Dihasilkan:

text
listener_sinkron_jalan: false
job_masuk_antrean: 0

Dua baris terakhir adalah bukti bahwa tidak ada listener yang berjalan: log konfirmasi-dikirim tidak pernah tertulis dan tabel jobs tetap kosong — padahal listener ber-queue SendOrderReport terdaftar. Sementara assertion assertDispatched(OrderPlaced::class) lulus karena fake mencatat dispatch-nya. Memutus koneksi antara dispatch dan eksekusi inilah tujuan fake: test menjadi cepat, deterministik, dan bebas efek samping seperti email sungguhan.

Inspeksi Payload dengan Closure #

Assertion menerima closure sebagai pemeriksa tambahan. Closure menerima instance event sehingga isi payload bisa diperiksa field demi field:

php
public function test_inspeksi_payload_dengan_closure(): void
{
    Event::fake();

    OrderPlaced::dispatch('INV-1002', '[email protected]', 175000);
    OrderPlaced::dispatch('INV-1002', '[email protected]', 25000);

    Event::assertDispatched(OrderPlaced::class, function (OrderPlaced $event) {
        return $event->orderId === 'INV-1002'
            && $event->email === '[email protected]';
    });

    Event::assertDispatchedTimes(OrderPlaced::class, 2);

    Event::assertNotDispatched(OrderPlaced::class, function (OrderPlaced $event) {
        return $event->total < 0;
    });
}

Closure pada assertDispatched diteruskan ke setiap instance yang tercatat dan lulus bila minimal satu lolos pemeriksaan. assertDispatchedTimes mengunci jumlah kemunculan — di atas tepat dua karena ada dua dispatch. Varian assertNotDispatched dengan closure memastikan tidak ada dispatch yang memenuhi kondisi tertentu, lebih presisi daripada sekadar memeriksa keberadaan event. Peta assertion yang paling sering kita pakai:

AssertionMemeriksa
assertDispatched(Event::class)minimal satu dispatch event itu
assertDispatched(Event::class, fn ($e) => ...)payload lolos inspeksi closure
assertDispatchedTimes(Event::class, 2)jumlah dispatch tepat
assertNotDispatched(Event::class)event itu tidak pernah terkirim
assertNothingDispatched()tidak ada event sama sekali

Baris terakhir tabel diuji oleh test kedua pada suite di akhir chapter ini — fake kosong merekam nol event: sebuah fake segar tanpa dispatch apa pun, sehingga assertNothingDispatched() lulus.

Fake Selektif #

Mem-fake semua event kadang terlalu kasar — test yang mengamati interaksi sekaligus efek nyata butuh kontrol lebih halus. Event::fake([OrderPlaced::class]) hanya mencegat event yang disebut, event lain tetap hidup:

php
public function test_fake_selektif_membiarkan_event_lain_tetap_hidup(): void
{
    Event::fake([OrderPlaced::class]);

    OrderPlaced::dispatch('INV-1003', '[email protected]', 900000);
    OrderShipped::dispatch('INV-1003', 'JNE Express');

    Event::assertDispatched(OrderPlaced::class, fn ($e) => $e->total === 900000);

    $this->assertNotContains('konfirmasi-dikirim', $this->terlog);
    $this->assertContains('audit: order-dikirim', $this->terlog);
}

Hasilnya campuran yang tepat: listener milik OrderPlaced tidak berjalan (konfirmasi-dikirim absen), sementara OrderShipped yang tidak di-fake tetap diproses oleh subscriber audit — baris audit: order-dikirim hadir di log. Variasi ini juga berguna saat event model Eloquent yang berisik ikut ter-fake di tengah test alur bisnis.

assertListening untuk Verifikasi Wiring #

Bagian dari kepercayaan pada sistem event adalah keyakinan bahwa event tertentu terhubung ke listener tertentu. Event::assertListening() memeriksa registrasi itu tanpa perlu dispatch:

php
public function test_assert_listening_memeriksa_wiring(): void
{
    Event::fake();

    Event::assertListening(OrderPlaced::class, SendOrderConfirmation::class);
    Event::assertListening(OrderPlaced::class, SendOrderReport::class);
    Event::assertListening(OrderPlaced::class, [
        OrderActivitySubscriber::class, 'recordOrderPlaced',
    ]);
}

Listener class diperiksa cukup dengan nama classnya, sedangkan method subscriber diperiksa lewat array [class, method] — bentuk yang sama persis dengan cara Laravel me-register-nya. Assertion ini melindungi kita dari refactoring yang tidak sengaja memutus tautan event-listener, misalnya type hint yang berubah atau listener yang pindah folder.

Test Integrasi Tanpa Fake #

Fake tidak pernah membuktikan handle() benar. Untuk itu kita lepas fake sepenuhnya dan mengamati efek samping sungguhan — log yang ditulis listener sinkron dan job yang masuk antrean untuk listener queued:

php
public function test_integrasi_tanpa_fake_memicu_listener_sungguhan(): void
{
    config(['queue.default' => 'database']);

    OrderPlaced::dispatch('INV-1004', '[email protected]', 320000);

    $this->assertContains('konfirmasi-dikirim', $this->terlog);
    $this->assertContains('analytics-order', $this->terlog);
    $this->assertContains('audit: order-dibuat', $this->terlog);
    $this->assertSame(1, DB::table('jobs')->count());
    $this->assertNotContains('laporan-order', $this->terlog);
}

Tiga listener sinkron membuktikan diri lewat log: konfirmasi, analytics, dan audit subscriber. Listener queued SendOrderReport tidak menulis log (belum dijalankan worker) — keberadaannya terbukti lewat satu baris di tabel jobs. Pengaturan config(['queue.default' => 'database']) penting karena phpunit.xml bawaan memakai koneksi sync yang mengeksekusi job langsung di dalam test. Hasil seluruh suite ketika dijalankan:

text
  PASS  Tests\Feature\EventChapterTest
  ✓  fake merekam dispatch tanpa mengeksekusi listener    0.14s
  ✓  fake kosong merekam nol event                        0.01s
  ✓  inspeksi payload dengan closure                       0.01s
  ✓  fake selektif membiarkan event lain tetap hidup      0.01s
  ✓  assert listening memeriksa wiring                    0.01s
  ✓  integrasi tanpa fake memicu listener sungguhan        0.01s

  Tests:    6 passed (18 assertions)

Enam test selesai dalam 0,25 detik — lima di antaranya berbasis fake, satu test integrasi. Proporsi seperti ini adalah pola sehat untuk sistem event: mayoritas test cepat mengunci perilaku dispatch, sedikit test integrasi memverifikasi listener sungguhan bekerja.

Dengan ini perjalanan kita di section events tuntas: dari konsep decoupling, event class, listener, discovery, queue, subscriber, hingga event model dan strategi pengujian. Pola “umumkan fakta, biarkan pihak lain bereaksi” ini akan terus muncul di bagian Course lanjutan — dari notifikasi sampai broadcasting — dan kini kita memegang seluruh pernak-perniknya.