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.

Panduan teknis testing Laravel dengan Pest mencakup mocking, architecture test, dan persiapan wawancara teknis 2026

Kualitas perangkat lunak PHP bergantung pada ketangguhan test suite sama besarnya dengan kualitas kode aplikasi itu sendiri. Dalam ekosistem 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, 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.

tests/Pest.phpphp
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.

tests/Feature/UserRegistrationTest.phpphp
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.

tests/Unit/PriceCalculatorTest.phpphp
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.

tests/Feature/OrderProcessingTest.phpphp
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.

tests/Feature/PaymentGatewayTest.phpphp
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 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.

tests/Architecture/ArchitectureTest.phpphp
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.

tests/Architecture/LaravelPresetTest.phpphp
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.

Siap menguasai wawancara Laravel Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

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.

tests/Feature/ArticlePublishingTest.phpphp
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.

tests/Feature/ApiAuthenticationTest.phpphp
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.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tag

#laravel
#testing
#pest
#php
#mocking
#best-practices
#interview

Bagikan

Artikel terkait