Satu aksi bisnis di Laravel sering memicu orkestra belakang layar: event diterbitkan, job masuk antrean, notifikasi terkirim, chain pekerjaan berjalan berurutan. Chapter Pengujian Queue
dan Pengujian Events
telah membahas masing-masing fake secara terpisah; di sini kita gabungkan semuanya dalam satu endpoint nyata — POST /posts/{post}/terbit — lalu mengujinya dengan tiga strategi berbeda: kombinasi fake, Bus::fake untuk chain dan after-response, dan integrasi penuh dengan worker sungguhan.
Endpoint yang Memicu Segalanya #
Controller terbit kita sengaja memakai semua kanal sekaligus:
public function terbit(Request $request, Post $post): JsonResponse
{
$post->update(['published_at' => now()]);
PostDiterbitkan::dispatch($post);
dispatch((new KirimNewsletterJob($post))->onQueue('newsletter'));
Bus::chain([
new BuatSnapshotJob($post),
new BersihkanCacheJob($post),
])->dispatch();
dispatch_sync(new SinkronStatistikJob($post));
Bus::dispatchAfterResponse(new LaporanAkhirJob($post));
User::query()
->where('id', '!=', $post->user_id)
->get()
->each->notify(new PostBaruNotification($post));
return response()->json(['terbit' => true]);
}Setiap baris adalah kandidat assertion yang berbeda, dan setiap fake mencatat kanalnya sendiri. Perhatikan bahwa keempat cara pengiriman sengaja dipakai bersamaan dalam satu method: dispatch() untuk job ber-queue, dispatch_sync() untuk pekerjaan yang harus tuntas sebelum response terkirim, Bus::chain() untuk pekerjaan berurutan, dan Bus::dispatchAfterResponse() untuk pekerjaan yang boleh menunggu response sampai ke pengguna. Notifikasi pengikut di baris terakhir juga ber-queue, karena PostBaruNotification mengimplementasikan ShouldQueue — detail ini nanti terlihat di test integrasi.
Kombinasi Queue, Event, dan Notification Fake #
Test pertama memasang tiga fake sekaligus — Queue::fake(), Event::fake(), Notification::fake() — lalu menguji satu request:
public function test_fake_merekam_semua_jalur_belakang_layar(): void
{
Queue::fake();
Event::fake();
Notification::fake();
$post = $this->postBudi();
$siti = User::firstWhere('email', '[email protected]');
$this->post("/posts/{$post->id}/terbit")->assertOk();
Event::assertDispatched(PostDiterbitkan::class, function (PostDiterbitkan $event) use ($post) {
return $event->post->id === $post->id;
});
Event::assertDispatchedTimes(PostDiterbitkan::class, 1);
Queue::assertPushedOn('newsletter', KirimNewsletterJob::class,
fn (KirimNewsletterJob $job) => $job->post->id === $post->id);
Queue::assertPushedWithChain(BuatSnapshotJob::class, [
new BersihkanCacheJob($post),
]);
Notification::assertSentTo(
$siti,
PostBaruNotification::class,
fn (PostBaruNotification $notifikasi) => $notifikasi->post->id === $post->id
);
}Setiap assertion membawa closure yang memeriksa payload — event membawa post yang benar, job newsletter masuk antrean bernama newsletter, chain tercatat lengkap dengan assertPushedWithChain, dan notifikasi terkirim ke Siti (pengikut) dengan referensi post yang tepat. Tidak ada satu pun kode belakang layar yang dieksekusi; yang diuji adalah keputusan pengiriman.
Bus::fake untuk Chain, Sync, dan After-Response #
Chain dan batch dikirim lewat command bus, sehingga assertion terbaiknya milik Bus::fake. Test kedua memakai satu fake ini saja:
Bus::fake();
$this->post("/posts/{$post->id}/terbit")->assertOk();
Bus::assertDispatched(KirimNewsletterJob::class,
fn (KirimNewsletterJob $job) => $job->post->id === $post->id);
Bus::assertChained([
new BuatSnapshotJob($post),
new BersihkanCacheJob($post),
]);
Bus::assertDispatchedSync(SinkronStatistikJob::class);
Bus::assertDispatchedAfterResponse(LaporanAkhirJob::class);Empat assertion ini menjawab empat pertanyaan berbeda: job biasa terkirim dengan payload benar (assertDispatched), urutan chain lengkap (assertChained — membandingkan anggota dan urutannya), job sync benar-benar dikirim sinkron (assertDispatchedSync), dan job after-response terdaftar untuk dijalankan setelah response terkirim (assertDispatchedAfterResponse). Inilah pembeda mendasar dua fake: Queue::fake beroperasi di level antrean, Bus::fake di level command bus yang mencakup chain, batch, sync, dan after-response. Saat keduanya aktif bersamaan, dispatch tertangkap oleh Bus::fake lebih dulu dan tidak pernah sampai ke antrean — karena itu suite kita memisahkan keduanya dalam dua test yang berbeda, masing-masing dengan kombinasi fake yang sesuai dengan yang diperiksa.
Integrasi: Worker Sungguhan di Dalam Test #
Fake tidak pernah membuktikan handle() benar. Test ketiga melepas semua fake, mengarahkan queue ke database, lalu menjalankan worker sungguhan:
public function test_integrasi_menjalankan_worker_sungguhan(): void
{
config(['queue.default' => 'database']);
$post = $this->postBudi();
$this->post("/posts/{$post->id}/terbit")->assertOk();
Artisan::call('queue:work', [
'--queue' => 'newsletter,default',
'--stop-when-empty' => true,
'--max-time' => 20,
]);
$this->assertContains('newsletter: Belajar Queue dan Event', $this->terlog);
$this->assertContains('snapshot: Belajar Queue dan Event', $this->terlog);
$this->assertContains('cache: Belajar Queue dan Event', $this->terlog);
$siti = User::firstWhere('email', '[email protected]');
$this->assertDatabaseHas('notifications', [
'notifiable_type' => User::class,
'notifiable_id' => $siti->id,
]);
$this->assertSame(0, DB::table('jobs')->count());
$this->assertSame(0, DB::table('failed_jobs')->count());
}Kolektor $this->terlog diisi lewat Log::listen() di setUp(), sehingga kita bisa memeriksa log mana yang benar-benar ditulis handle() tiap job. Notifikasi — yang ber-queue — turut diproses worker, terbukti dari barisnya di tabel notifications. Eksekusi nyatanya:
jobs_sebelum_worker: 4
jobs_sesudah_worker: 0
PASS Tests\Feature\TerbitTest
✓ fake merekam semua jalur belakang layar 0.17s
✓ bus fake merekam chain sync dan after response 0.01s
✓ integrasi menjalankan worker sungguhan 3.06s
Tests: 3 passed (21 assertions)
Duration: 3.33sEmpat job menunggu sebelum worker berjalan — newsletter, snapshot ber-chain, notifikasi ber-queue, dan laporan after-response — lalu nol tersisa setelahnya. Perhatikan pula durasinya: dua test fake selesai di bawah 0,2 detik, test integrasi butuh 3 detik.
Fake atau Integrasi #
| Yang diuji | Alat | Alasan |
|---|---|---|
| Job/event/notifikasi terkirim dengan payload benar | Queue::fake, Event::fake, Notification::fake | Cepat, tanpa efek samping |
| Urutan chain, dispatch sync, after-response | Bus::fake | Assertion bus tidak bisa dilihat antrean |
Isi handle() benar-benar bekerja | tanpa fake + queue:work --stop-when-empty | Hanya eksekusi nyata yang membuktikannya |
| Notifikasi/pengirim email terekam | Notification::fake, Mail::fake | Tidak mengirim ke dunia luar |
Fake mencatat keputusan, integrasi membuktikan eksekusi — dua sisi yang sama pentingnya. Kabar baiknya, sebagian besar boundary eksternal (waktu, jaringan, filesystem) juga sudah punya fake resmi, dan sebagian lagi kita buat sendiri lewat mock. Chapter Mocking dan Fakes berikutnya merangkum seluruh perangkatnya. Rujukan assertion ada di dokumentasi Mocking .