# Laravel Testing 2026: Pest, Mocking, dan Pertanyaan Wawancara Teknis > Panduan lengkap testing Laravel dengan Pest di 2026: unit test, feature test, mocking facade, architecture test, mutation testing, dan pertanyaan wawancara teknis untuk developer PHP. - Published: 2026-06-04 - Updated: 2026-06-04 - Author: SharpSkill - Tags: laravel, testing, pest, php, mocking, best-practices, interview - Reading time: 9 min --- Kualitas perangkat lunak PHP bergantung pada ketangguhan test suite sama besarnya dengan kualitas kode aplikasi itu sendiri. Dalam ekosistem [Laravel](/technologies/laravel), Pest telah menjadi framework testing standar, menggantikan penggunaan PHPUnit secara langsung di sebagian besar proyek profesional. Di tahun 2026, penguasaan Pest 4, mocking facade, dan architecture test merupakan prasyarat teknis yang tidak bisa ditawar dalam proses rekrutmen developer PHP. Tim yang men-deploy Laravel di lingkungan production mengharapkan kandidat mampu menulis test yang ekspresif, menjaga cakupan test yang bermakna, dan menyusun test suite secara profesional. Artikel ini membahas secara mendalam praktik testing Laravel dengan Pest, mulai dari konfigurasi awal hingga mutation testing, termasuk mocking tingkat lanjut dan architecture test. Setiap bagian mencerminkan kompetensi yang langsung dapat dievaluasi dalam [wawancara teknis Laravel](/blog/laravel/laravel-php-interview-questions), disertai contoh kode yang dapat langsung diterapkan dalam latihan praktis maupun proyek nyata. > **Mengapa Pest, Bukan PHPUnit Langsung?** > > Pest dibangun di atas PHPUnit tetapi menawarkan sintaks ekspresif yang terinspirasi dari Jest dan RSpec. Rantai `expect()->toBe()` menggantikan assertion klasik seperti `assertEquals()`, blok `describe/it` menyusun test secara mudah dibaca, dan fitur bawaan seperti architecture test serta mutation testing mengurangi ketergantungan pada paket pihak ketiga. Pest tetap sepenuhnya kompatibel dengan PHPUnit: setiap test PHPUnit yang sudah ada berjalan tanpa modifikasi dalam suite Pest. ## Menyiapkan Pest 4 di Laravel 12 Konfigurasi Pest dalam proyek Laravel berpusat pada file `tests/Pest.php`, yang mendefinisikan trait dan base class yang diterapkan secara otomatis ke setiap file test. Sentralisasi ini menghindari pengulangan deklarasi umum dan menjamin konsistensi lingkungan test di seluruh suite. ```php // tests/Pest.php use Illuminate\Foundation\Testing\RefreshDatabase; pest() ->extend(Tests\TestCase::class) ->use(RefreshDatabase::class) ->in('Feature'); ``` Method `extend()` mengasosiasikan class `TestCase` Laravel ke semua test, menyediakan akses ke method HTTP (`getJson`, `postJson`), autentikasi simulasi (`actingAs`), dan assertion response. Trait `RefreshDatabase` mereset database di antara setiap test menggunakan transaksi, menjamin isolasi penuh tanpa biaya reload migrasi di setiap eksekusi. Directive `->in('Feature')` membatasi penerapan konfigurasi ini hanya pada feature test. Unit test yang berada di `tests/Unit/` mewarisi lingkungan yang lebih ringan, tanpa koneksi database, sehingga eksekusinya lebih cepat. Pemisahan arsitektural antara unit test dan feature test ini mencerminkan piramida testing klasik dan merupakan poin evaluasi yang umum dalam wawancara teknis. ## Unit Test vs Feature Test Feature test dalam Laravel memverifikasi perilaku lengkap sebuah endpoint, dari request HTTP yang masuk hingga response JSON atau redirect. Pest menyederhanakan penulisan test ini berkat sintaks fluent yang memungkinkan perangkaian assertion status, assertion struktur JSON, dan verifikasi database. Test berikut memvalidasi registrasi pengguna melalui API REST. ```php // tests/Feature/UserRegistrationTest.php use App\Models\User; it('registers a new user with valid data', function () { $response = $this->postJson('/api/register', [ 'name' => 'Jane Doe', 'email' => 'jane@example.com', 'password' => 'SecurePass123!', 'password_confirmation' => 'SecurePass123!', ]); $response->assertStatus(201) ->assertJsonStructure(['user' => ['id', 'name', 'email']]); expect(User::where('email', 'jane@example.com')->exists())->toBeTrue(); }); ``` Beberapa elemen perlu diperhatikan secara khusus. Method `postJson()` mengirim request POST dengan header JSON yang dikonfigurasi secara otomatis, mensimulasikan klien API sesungguhnya. Assertion `assertJsonStructure` memvalidasi keberadaan key yang diharapkan tanpa memaksakan nilai spesifik, menjadikan test ini tahan terhadap perubahan minor pada response. Assertion terakhir melalui `expect()` mengonfirmasi bahwa pengguna benar-benar tersimpan di database, memverifikasi seluruh rantai pemrosesan. Pendekatan test end-to-end di level HTTP ini merupakan standar di Laravel. Pendekatan ini memvalidasi routing, middleware, validasi data, logika controller, dan persistensi secara bersamaan. Kegagalan di lapisan mana pun akan terdeteksi dalam test, memberikan jaring pengaman menyeluruh untuk refactoring. Unit test menargetkan class atau method individual, tanpa interaksi dengan database, filesystem, atau layanan eksternal. Unit test berjalan dalam hitungan milidetik dan membentuk dasar paling andal dari piramida testing. Blok `describe/it` dari Pest menyusun test-test ini secara mudah dibaca. ```php // tests/Unit/PriceCalculatorTest.php use App\Services\PriceCalculator; describe('PriceCalculator', function () { it('applies a percentage discount correctly', function () { $calculator = new PriceCalculator(); // 20% off a 150.00 base price $result = $calculator->applyDiscount(150.00, 20); expect($result)->toBe(120.00); }); it('rejects negative discount values', function () { $calculator = new PriceCalculator(); expect(fn () => $calculator->applyDiscount(100.00, -5)) ->toThrow(InvalidArgumentException::class); }); }); ``` Blok `describe` mengelompokkan test yang berkaitan dengan class atau fungsionalitas yang sama, memudahkan pembacaan laporan eksekusi. Assertion `toThrow()` memvalidasi bahwa exception dilempar dengan benar untuk input yang tidak valid, sebuah pattern defensif yang esensial dalam logika bisnis. Penggunaan closure pada `expect()` memungkinkan penangkapan exception tanpa menghentikan test. Dalam wawancara teknis, kemampuan membedakan apa yang termasuk unit test (logika kalkulasi, transformasi data, validasi aturan bisnis) dan apa yang termasuk feature test (interaksi HTTP, integrasi dengan database) merupakan indikator kunci kematangan teknis seorang developer. ## Mocking dengan Facade dan Mockery Laravel menyediakan sistem facade yang menyederhanakan akses ke service framework. Untuk testing, facade ini menawarkan method `fake()` yang menggantikan implementasi asli dengan test double yang mampu merekam panggilan yang diterima tanpa menghasilkan side effect. Mekanisme ini fundamental untuk menguji proses yang mengirim email, men-dispatch job, atau memicu notifikasi. ```php // tests/Feature/OrderProcessingTest.php use App\Models\Order; use App\Models\User; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use App\Mail\OrderConfirmation; use App\Jobs\ProcessPayment; it('dispatches payment job and sends confirmation email', function () { // Fake both Mail and Queue facades Mail::fake(); Queue::fake(); $user = User::factory()->create(); $order = Order::factory()->for($user)->create([ 'total' => 99.99, 'status' => 'pending', ]); // Act: confirm the order via HTTP $this->actingAs($user) ->postJson("/api/orders/{$order->id}/confirm") ->assertOk(); // Assert: payment job was dispatched with correct amount Queue::assertPushed(ProcessPayment::class, function ($job) use ($order) { return $job->order->id === $order->id && $job->order->total === 99.99; }); // Assert: confirmation email was sent to the user Mail::assertSent(OrderConfirmation::class, function ($mail) use ($user) { return $mail->hasTo($user->email); }); }); ``` Pemanggilan `Mail::fake()` dan `Queue::fake()` masing-masing mengintersepi pengiriman email dan dispatch job selama durasi test berlangsung. Tidak ada email sungguhan yang terkirim, tidak ada job yang dieksekusi di background. Assertion `assertPushed` dan `assertSent` kemudian memverifikasi bahwa class yang tepat telah di-dispatch dengan parameter yang diharapkan. Closure yang dikirimkan ke `Queue::assertPushed` mengilustrasikan pattern tingkat lanjut: closure ini memungkinkan verifikasi bukan hanya bahwa job telah di-dispatch, tetapi juga bahwa property-nya berisi nilai yang benar. Granularitas assertion ini mendeteksi bug halus di mana objek yang salah dikirimkan ke job, kesalahan umum saat refactoring. > **Lakukan Mocking di Batas Sistem** > > Mocking yang berlebihan menjadikan test rapuh dan terikat erat pada detail implementasi. Praktik terbaik adalah melakukan mock hanya pada batas sistem: panggilan API eksternal, gateway pembayaran, layanan email, dan integrasi pihak ketiga lainnya. Logika bisnis internal sebaiknya diuji melalui input dan output yang sesungguhnya, bukan melalui mock pada setiap dependensi internal. Untuk service yang bukan facade Laravel, Mockery menawarkan kontrol granular terhadap perilaku test double. Kasus ini sering muncul pada klien layanan pihak ketiga seperti Stripe, Twilio, atau API internal. ```php // tests/Feature/PaymentGatewayTest.php use App\Services\PaymentGateway; use App\Services\StripeClient; it('charges the customer through the payment gateway', function () { // Create a mock of the Stripe client $stripeClient = Mockery::mock(StripeClient::class); $stripeClient->shouldReceive('charge') ->once() ->with('cus_abc123', 5000, 'usd') ->andReturn(['status' => 'succeeded', 'id' => 'ch_xyz']); // Bind the mock in the container $this->app->instance(StripeClient::class, $stripeClient); $gateway = app(PaymentGateway::class); $result = $gateway->processCharge('cus_abc123', 50.00); expect($result['status'])->toBe('succeeded'); }); ``` Method `Mockery::mock()` membuat test double yang mengintersepi pemanggilan method. Rangkaian `shouldReceive()->once()->with()->andReturn()` mendefinisikan kontrak ketat: method `charge` harus dipanggil tepat satu kali, dengan parameter yang ditentukan, dan akan mengembalikan nilai yang didefinisikan. Setiap penyimpangan menyebabkan test gagal. Instruksi `$this->app->instance()` mendaftarkan mock ke service container Laravel. Ketika `PaymentGateway` meminta instance `StripeClient` melalui dependency injection, container menyediakan mock alih-alih implementasi sesungguhnya. Mekanisme ini berlandaskan prinsip [dependency inversion](/technologies/laravel/interview-questions/service-container-di) dan merupakan kunci testabilitas dalam aplikasi Laravel yang dirancang dengan baik. Perbedaan antara jumlah yang dikirim ke `processCharge` (50.00 dalam dollar) dan yang dikirim ke `charge` (5000 dalam sen) mengungkap logika konversi di dalam `PaymentGateway`. Test ini secara implisit memvalidasi transformasi tersebut, mendeteksi regresi apa pun dalam kalkulasi tanpa perlu memeriksa langsung kode class tersebut. ## Architecture Test dengan Preset Pest Pest memperkenalkan fitur unik dalam ekosistem PHP: architecture test. Test ini memverifikasi bahwa source code mematuhi konvensi struktural yang ditetapkan tim, tanpa mengeksekusi logika aplikasi. Pelanggaran terdeteksi secara statis, layaknya linter tingkat lanjut. ```php // tests/Architecture/ArchitectureTest.php arch('controllers do not use Eloquent directly') ->expect('App\Http\Controllers') ->not->toUse('Illuminate\Database\Eloquent'); arch('services are final classes') ->expect('App\Services') ->toBeFinal(); arch('no debugging functions in production code') ->expect(['dd', 'dump', 'var_dump', 'ray']) ->not->toBeUsed(); ``` Test pertama memaksakan pemisahan arsitektural yang ketat: controller tidak boleh memanipulasi Eloquent secara langsung, memaksa penggunaan service layer atau repository. Pembatasan ini mencegah penumpukan logika bisnis di controller, anti-pattern klasik pada aplikasi Laravel yang tidak terstruktur dengan baik. Test kedua menjamin bahwa class service dideklarasikan sebagai `final`, mencegah pewarisan tak terkendali yang mempersulit maintenance. Test ketiga mendeteksi fungsi debugging yang terlupakan dalam kode production, masalah yang menyebabkan kebocoran informasi di lingkungan production. Pest juga menyediakan preset architecture yang sudah dikonfigurasi untuk mencakup konvensi standar Laravel. ```php // tests/Architecture/LaravelPresetTest.php arch()->preset()->laravel(); arch()->preset()->security(); arch()->preset()->php(); ``` Preset `laravel()` memverifikasi bahwa controller meng-extend base class yang benar, model berada di namespace yang tepat, dan konvensi penamaan dipatuhi. Preset `security()` mendeteksi penggunaan fungsi berbahaya seperti `eval()`, `exec()`, atau `shell_exec()`. Preset `php()` memaksakan praktik terbaik umum bahasa PHP. Dalam wawancara teknis, pengetahuan tentang architecture test membedakan kandidat yang memiliki visi menyeluruh tentang kualitas perangkat lunak dari mereka yang hanya mengandalkan feature test. Test ini mengkodifikasi keputusan arsitektural tim dan menjadikannya dapat dieksekusi secara otomatis dalam pipeline CI. ## Mutation Testing Code coverage mengukur baris kode mana yang dieksekusi selama test, tetapi tidak mengatakan apa pun tentang kualitas assertion. Mutation testing mengisi celah ini dengan memodifikasi source code secara sistematis (mengganti operator, menghapus kondisi, mengubah return value) dan memverifikasi bahwa test mendeteksi setiap mutasi. Test yang tetap lolos meskipun ada mutasi berarti assertion-nya kurang presisi. Pest mengintegrasikan mutation testing secara native sejak versi 3, menjadikan praktik ini dapat diakses tanpa konfigurasi tambahan. ```bash # Run mutation testing on a specific class php artisan test --mutate --class=App\\Services\\PriceCalculator ``` Perintah ini menganalisis class target, menghasilkan mutan, dan menjalankan test suite untuk masing-masing mutan. Laporan menunjukkan mutation score: persentase mutan yang terdeteksi oleh test yang ada. Contoh berikut mengilustrasikan perbedaan antara test dengan coverage tinggi tetapi mutation score rendah dan test yang benar-benar robust. ```php // Example: this test has high coverage but low mutation score it('calculates shipping cost', function () { $cost = calculateShipping(weight: 5.0, zone: 'domestic'); // Only checks that the result is numeric expect($cost)->toBeFloat(); }); // Improved: catches mutations by asserting the exact value it('calculates domestic shipping for 5kg package', function () { $cost = calculateShipping(weight: 5.0, zone: 'domestic'); // Exact assertion catches operator and value mutations expect($cost)->toBe(12.50); }); ``` Test pertama tetap lolos meskipun formula kalkulasi diubah total: bilangan float apa pun memenuhi assertion `toBeFloat()`. Test kedua memaksakan nilai eksak yang diharapkan, mendeteksi mutasi apa pun dalam logika kalkulasi. Perbedaan antara assertion longgar dan assertion presisi ini merupakan konsep fundamental yang kerap dievaluasi oleh pewawancara teknis. Mutation testing mengungkap area kode di mana coverage kuantitatif menutupi kelemahan kualitatif. Mutation score 90% atau lebih mengindikasikan test suite yang benar-benar andal, mampu mendeteksi regresi halus yang lolos dari line coverage 100%. ## Pola Testing Database dan Factory Factory dalam Laravel menyederhanakan pembuatan data test dengan nilai default yang konsisten. Factory memungkinkan spesifikasi hanya atribut yang relevan untuk test yang sedang berjalan, menjadikan maksud test eksplisit. Method `for()` dan `has()` mengelola relasi antar model secara otomatis, mengeliminasi kode repetitif untuk pembuatan data. Test berikut memvalidasi workflow lengkap publikasi artikel. ```php // tests/Feature/ArticlePublishingTest.php use App\Models\Article; use App\Models\User; it('publishes a draft article and updates the timestamp', function () { $author = User::factory()->create(['role' => 'editor']); $article = Article::factory() ->for($author, 'author') ->draft() ->create(['title' => 'Testing Best Practices']); $this->actingAs($author) ->patchJson("/api/articles/{$article->id}/publish") ->assertOk() ->assertJsonPath('data.status', 'published'); $article->refresh(); expect($article->status)->toBe('published') ->and($article->published_at)->not->toBeNull() ->and($article->published_at->isToday())->toBeTrue(); }); ``` Factory `draft()` membuat artikel dengan status draft, mensimulasikan konteks awal. Assertion `assertJsonPath` memverifikasi nilai spesifik dalam response JSON tanpa memaksakan seluruh struktur. Pemanggilan `refresh()` memuat ulang model dari database, menjamin bahwa assertion dilakukan terhadap state yang tersimpan dan bukan cache lokal. Rantai assertion Pest dengan `->and()` memungkinkan verifikasi beberapa property dalam satu ekspresi fluent, meningkatkan keterbacaan test. Verifikasi `isToday()` memanfaatkan kapabilitas Carbon yang terintegrasi di Laravel untuk memvalidasi bahwa timestamp publikasi sesuai dengan hari saat ini. > **Parallel Testing untuk Suite yang Lebih Cepat** > > Pest mendukung parallel testing melalui flag `--parallel`, yang menjalankan test secara bersamaan di beberapa proses. Untuk proyek besar dengan ratusan test, fitur ini dapat mengurangi waktu eksekusi secara signifikan. Pastikan setiap test terisolasi sepenuhnya dan tidak bergantung pada state bersama, karena test yang berjalan secara paralel menggunakan database terpisah. ## Pertanyaan Wawancara Teknis Pertanyaan testing dalam wawancara Laravel mengevaluasi pemahaman terhadap mekanisme di balik layar, bukan sekadar hafalan sintaks. Poin-poin berikut mencakup topik yang paling sering dibahas. **Apa perbedaan antara RefreshDatabase dan DatabaseMigrations?** Trait `RefreshDatabase` menggunakan transaksi database untuk membatalkan perubahan setelah setiap test, yang secara signifikan lebih cepat. `DatabaseMigrations` menjalankan migrasi penuh sebelum setiap test dan memotong tabel setelahnya, lebih lambat tetapi diperlukan dalam kasus tertentu di mana transaksi tidak mencukupi, misalnya test yang melibatkan beberapa koneksi database. **Mengapa menggunakan factory alih-alih insert manual?** Factory mengenkapsulasi pembuatan objek test dengan nilai default yang konsisten. Factory memungkinkan spesifikasi hanya atribut yang relevan untuk test yang bersangkutan, menjadikan maksud test eksplisit. Factory mengelola relasi antar model secara otomatis melalui method `for()` dan `has()`, menghilangkan kode repetitif pembuatan data. **Bagaimana menguji middleware tanpa menguji seluruh controller?** Laravel memungkinkan instansiasi middleware secara langsung dan pemanggilan method `handle()` dengan request buatan. Pendekatan ini mengisolasi logika middleware dari logika controller. Alternatifnya, feature test yang ditargetkan dengan assertion pada status HTTP memungkinkan verifikasi perilaku middleware dalam konteksnya. **Kapan sebaiknya menggunakan Mockery daripada fake facade?** Fake facade (`Mail::fake()`, `Queue::fake()`) memadai untuk service bawaan Laravel. Mockery diperlukan untuk service pihak ketiga (klien API, SDK eksternal) yang bukan facade. Aturan umumnya: gunakan fake Laravel jika tersedia, Mockery untuk yang lainnya. **Apa yang divalidasi architecture test yang tidak tercakup oleh test biasa?** Architecture test memverifikasi struktur dan dependensi kode tanpa mengeksekusinya. Test ini mendeteksi pelanggaran konvensi (controller yang menggunakan Eloquent langsung, fungsi debug yang terlupakan, service yang tidak dideklarasikan final) yang tidak dapat ditangkap oleh feature test karena kode tetap berfungsi dengan benar meskipun terjadi pelanggaran arsitektural. **Bagaimana menginterpretasikan mutation score rendah meskipun code coverage tinggi?** Mutation score rendah dengan coverage tinggi berarti test mengeksekusi kode tetapi tidak memverifikasi hasilnya dengan presisi. Solusinya adalah mengganti assertion longgar (`toBeFloat`, `toBeArray`) dengan assertion presisi (`toBe(12.50)`, `toHaveCount(3)`) yang mendeteksi modifikasi pada logika bisnis. ## Pengujian HTTP Test HTTP mencakup tiga skenario fundamental dari setiap API yang terautentikasi: penolakan request tanpa autentikasi, response yang benar untuk pengguna yang terautentikasi, dan validasi field wajib. ```php // tests/Feature/ApiAuthenticationTest.php use App\Models\User; describe('API Authentication', function () { it('rejects unauthenticated requests with 401', function () { $this->getJson('/api/profile') ->assertUnauthorized(); }); it('returns the authenticated user profile', function () { $user = User::factory()->create([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); $this->actingAs($user) ->getJson('/api/profile') ->assertOk() ->assertJson([ 'data' => [ 'name' => 'John Doe', 'email' => 'john@example.com', ], ]); }); it('validates required fields on registration', function () { $this->postJson('/api/register', []) ->assertUnprocessable() ->assertJsonValidationErrors(['name', 'email', 'password']); }); }); ``` Assertion `assertUnauthorized()` memverifikasi status 401, sementara `assertUnprocessable()` sesuai dengan status 422 standar untuk error validasi. Penggunaan `assertJsonValidationErrors` memvalidasi bahwa error validasi diformat dengan benar dan dikaitkan dengan field yang bersangkutan. Pattern ini sangat penting untuk API yang dikonsumsi oleh klien frontend yang menampilkan pesan error di bawah setiap field formulir. Blok `describe` mengelompokkan skenario terkait autentikasi dalam satu struktur logis, memudahkan navigasi dan pemeliharaan test suite. Method `actingAs()` mensimulasikan sesi pengguna yang terautentikasi tanpa melalui proses login sesungguhnya, mengisolasi test dari mekanisme autentikasi dan memfokuskan assertion pada perilaku endpoint yang diuji. ## Kesimpulan Testing Laravel dengan Pest di 2026 merepresentasikan lebih dari sekadar keterampilan teknis tambahan: testing merupakan pilar kredibilitas profesional seorang developer PHP. Kemampuan menulis test yang ekspresif, melakukan mock dependensi eksternal dengan benar, dan memelihara konvensi arsitektural secara otomatis membedakan profil senior dari developer tingkat menengah. Poin-poin esensial untuk persiapan wawancara teknis: - **Konfigurasi Pest**: file `tests/Pest.php` memusatkan trait dan base class, memisahkan unit test dan feature test secara jelas - **Feature test**: kombinasi `postJson` / `assertStatus` / `expect` mencakup seluruh rantai pemrosesan sebuah endpoint - **Mocking facade**: `Mail::fake()`, `Queue::fake()` dan assertion terkait merupakan pattern standar untuk menguji proses asinkron - **Mockery**: untuk service pihak ketiga, injeksi mock melalui service container mengikuti prinsip dependency inversion - **Architecture test**: mengkodifikasi keputusan struktural tim dan menjadikannya dapat dieksekusi dalam pipeline CI/CD - **Mutation testing**: mutation score mengungkap kualitas sesungguhnya dari assertion, melampaui sekadar line coverage - **Factory dan assertion**: penguasaan factory Laravel dan assertion JSON yang granular mempercepat penulisan test yang mudah dipelihara Dalam proses rekrutmen Laravel, demonstrasi pendekatan terstruktur terhadap testing mengubah latihan teknis menjadi etalase kompetensi profesional. Setiap test yang ditulis dengan baik menunjukkan pemahaman mendalam terhadap framework dan ketelitian yang meyakinkan tim teknis tentang keandalan kode yang akan dihasilkan. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/laravel/laravel-testing-pest-mocking-best-practices