Laravel Testing dengan Pest 5 di 2026: TIA, Mocking, dan Pertanyaan Wawancara

Kuasai praktik terbaik pengujian Laravel dengan Pest 5, Test Impact Analysis, Mockery, facade fake, dan architecture test. Membahas unit test, feature test, strategi mocking, dan pertanyaan wawancara umum untuk developer Laravel.

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

Praktik terbaik pengujian Laravel telah berubah signifikan dengan Pest 5, mesin TIA (Test Impact Analysis), dan PHPUnit 13. Laravel 13 dilengkapi dukungan Pest bawaan, menjadikan pengalaman pengujian lebih ekspresif dan ringkas dibandingkan PHPUnit biasa. Panduan ini membahas pola-pola yang penting untuk aplikasi produksi maupun wawancara teknis.

Pest 5 Adalah Standar Baru

Pest 5 (dibangun di atas PHPUnit 13) adalah framework pengujian default untuk Laravel 13. Framework ini memerlukan PHP 8.4+ dan memperkenalkan mesin TIA, verifikasi AI agent bawaan, integrasi PHPStan, dan time-balanced sharding secara default.

Menyiapkan Pest 5 di Proyek Laravel 13

Setiap aplikasi Laravel 13 baru sudah menyertakan Pest secara otomatis. Untuk proyek yang sudah ada, migrasi dari Pest 4 hanya memerlukan beberapa menit karena pertimbangan utamanya adalah kompatibilitas PHPUnit 13. Konfigurasi berada di tests/Pest.php, di mana trait global dan helper method didaftarkan.

tests/Pest.phpphp
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()
    ->extend(Tests\TestCase::class)
    ->use(RefreshDatabase::class)
    ->in('Feature');

File tunggal ini menggantikan trait CreatesApplication lama dan pewarisan base test class. Trait RefreshDatabase membungkus setiap test dalam transaksi database, membatalkan perubahan secara otomatis.

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();
});

API expect() Pest dapat di-chain secara alami dengan assertion PHPUnit. Pemanggilan assertJsonStructure memvalidasi bentuk response, sedangkan expect()->toBeTrue() mengkonfirmasi state database. Kedua gaya ini dapat digunakan bersamaan tanpa konflik.

Test Impact Analysis: Fitur Utama Pest 5

Mesin TIA adalah yang membuat Pest 5 transformatif untuk codebase besar. Pada eksekusi pertama, Pest merekam test mana yang menyentuh file mana. Setiap eksekusi berikutnya hanya menjalankan test yang terpengaruh oleh perubahan dan memutar ulang hasil yang di-cache untuk yang lainnya.

bash
# First run: records baseline (requires PCOV or Xdebug)
php artisan test

# Subsequent runs: only affected tests execute
php artisan test

Test suite Laravel Cloud, yang berisi lebih dari 19.000 test, turun dari sekitar tiga menit menjadi lima detik dengan TIA diaktifkan. Mesin ini memahami lebih dari sekadar file PHP: ia mendeteksi perubahan di migrasi, template Blade, dan komponen JavaScript bersama.

TIA memerlukan coverage driver (PCOV atau Xdebug) untuk merekam baseline. Untuk pipeline CI, pemetaan yang di-cache bertahan antar eksekusi, membuat peningkatan performa bersifat kumulatif.

Unit Test vs Feature Test di Laravel

Perbedaan antara unit test dan feature test di Laravel menentukan bagian framework mana yang dijalankan. Unit test berjalan tanpa container aplikasi, membuatnya lebih cepat tetapi terbatas pada logika murni. Feature test menjalankan aplikasi lengkap, memungkinkan panggilan HTTP, query database, dan resolusi service.

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);
    });
});

Unit test menargetkan class terisolasi tanpa dependensi eksternal. Blok describe mengelompokkan assertion terkait, dan Pest 5 mendukung blok describe bersarang untuk hierarki test yang kompleks.

Feature test sebaiknya menjadi mayoritas dari test suite Laravel. Test ini menangkap bug integrasi yang terlewatkan unit test, seperti middleware route yang salah, aturan validasi yang hilang, atau relasi Eloquent yang rusak. Aturan umum: jika kode menyentuh database, layer HTTP, atau facade, tulis feature test.

Strategi Mocking dengan Facade dan Mockery

Mocking dalam pengujian Laravel mengisolasi kode yang diuji dari dependensi eksternal. Facade Laravel menyediakan implementasi fake bawaan untuk queue, event, notification, mail, dan storage. Mockery menangani sisanya.

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);
    });
});

Facade fake mencegat panggilan di level framework, mencegah email sungguhan dikirim atau job di-dispatch. Assertion berbasis closure memverifikasi data persis yang dikirim ke setiap komponen.

Mock di Batas Sistem

Hindari mocking class domain internal. Mock di batas sistem: API pihak ketiga, mail, queue, filesystem. Mocking berlebihan membuat test rapuh dan terikat erat pada detail implementasi.

Untuk dependensi yang bukan facade, inject melalui constructor dan gunakan Mockery:

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');
});

Binding mock dengan $this->app->instance() menggantikan implementasi asli selama durasi test. Mockery memverifikasi bahwa charge dipanggil tepat satu kali dengan argumen yang diharapkan.

Architecture Testing dengan Preset Pest

Pest 5 menyertakan architecture testing yang memberlakukan aturan struktural di seluruh codebase. Test ini berjalan terhadap AST, bukan runtime, membuatnya sangat cepat.

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();

Aturan pertama mencegah controller melakukan query database secara langsung, memberlakukan pola service layer. Aturan kedua memastikan service tidak dapat di-extend, mengurangi kompleksitas inheritance. Aturan ketiga menangkap statement debug yang tertinggal sebelum mencapai production.

Preset khusus Laravel juga tersedia:

tests/Architecture/LaravelPresetTest.phpphp
arch()->preset()->laravel();
arch()->preset()->security();
arch()->preset()->php();

Tiga baris ini memberlakukan puluhan aturan: model meng-extend base class yang benar, controller tidak mengandung business logic, tidak ada fungsi tidak aman seperti eval() atau md5() untuk hashing, dan konvensi PHP standar diikuti.

Siap menguasai wawancara Laravel Anda?

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

Mutation Testing untuk Memverifikasi Kualitas Test

Code coverage mengukur baris mana yang dieksekusi selama test. Mutation testing melangkah lebih jauh: ia memodifikasi source code dan memeriksa apakah test mendeteksi perubahan tersebut. Jika mutation bertahan, test suite memiliki celah.

bash
# Run mutation testing on a specific class
php artisan test --mutate --class=App\\Services\\PriceCalculator

Pest 5 memperkenalkan mutation seperti mengubah > menjadi >=, menghapus statement return, dan membalik kondisi boolean. Mutation score di bawah 80% biasanya menunjukkan bahwa test memverifikasi output tanpa memeriksa edge case.

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 lulus meskipun logika kalkulasi sepenuhnya salah, selama mengembalikan float. Test kedua langsung gagal ketika mutation mengubah formula. Assertion spesifik menghasilkan mutation score lebih tinggi.

Pola Pengujian Database dan Factory

Model factory Laravel menghasilkan data test realistis tanpa konstruksi array manual. Pest 5 dikombinasikan dengan factory menghasilkan setup data yang mudah dibaca dan dipelihara.

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 state draft() menetapkan nilai default untuk artikel yang belum dipublikasikan. Assertion expect() yang di-chain dengan ->and() terbaca seperti bahasa natural dan gagal dengan pesan deskriptif.

Parallel Testing dan Sharding

Jalankan php artisan test --parallel untuk mengeksekusi test di beberapa proses. Laravel secara otomatis membuat database test terpisah untuk setiap proses, mencegah konflik data. Time-balanced sharding Pest 5 mendistribusikan test berdasarkan waktu eksekusi aktual daripada jumlah, memastikan setiap CI shard selesai pada saat yang sama.

Pertanyaan Wawancara Teknis Laravel Testing

Wawancara teknis untuk posisi Laravel sering menguji pengetahuan testing. Berikut adalah pertanyaan yang paling sering muncul beserta poin-poin penting untuk jawaban yang kuat.

Apa perbedaan antara fake(), mock(), dan spy() dalam pengujian Laravel?

Facade fake() menggantikan seluruh facade dengan implementasi in-memory (Mail::fake, Queue::fake). mock() via Mockery menetapkan ekspektasi sebelum eksekusi dan gagal jika tidak terpenuhi. spy() merekam interaksi dan memungkinkan assertion setelah eksekusi, tanpa menetapkan ekspektasi di awal. Pilihan tergantung pada apakah test perlu memverifikasi perilaku (mock), merekam interaksi (spy), atau mencegah side effect (fake).

Bagaimana RefreshDatabase berbeda dari DatabaseTransactions?

RefreshDatabase menjalankan migrasi sekali dan membungkus setiap test dalam transaksi. DatabaseTransactions mengasumsikan database sudah memiliki skema yang benar dan hanya membungkus test dalam transaksi. RefreshDatabase lebih aman untuk pipeline CI di mana database mungkin belum ada. DatabaseTransactions lebih cepat ketika skema dijamin sudah terkini.

Kapan feature test lebih diutamakan daripada unit test?

Feature test sebaiknya mencakup kode apa pun yang berinteraksi dengan layer HTTP, database, atau service Laravel. Unit test dicadangkan untuk fungsi murni dan value object tanpa dependensi framework. Dalam aplikasi Laravel tipikal, feature test melebihi unit test dengan rasio sekitar 3:1 atau lebih tinggi. Ini mencerminkan realita bahwa sebagian besar kode Laravel secara inheren terintegrasi dengan framework.

Bagaimana Test Impact Analysis meningkatkan performa CI?

TIA merekam test mana yang menyentuh file source mana selama eksekusi pertama. Pada eksekusi berikutnya, Pest hanya menjalankan test yang terpengaruh oleh file yang berubah dan memutar ulang hasil yang di-cache untuk test yang tidak berubah. Suite besar yang memakan waktu bermenit-menit dapat selesai dalam hitungan detik. Tradeoff-nya: TIA memerlukan coverage driver (PCOV atau Xdebug) untuk membangun peta dependensi, dan eksekusi pertama memakan waktu lebih lama dari eksekusi normal.

Bagaimana mutation testing meningkatkan kualitas test melampaui code coverage?

Code coverage mengukur jalur eksekusi. Sebuah test dapat mencapai coverage 100% dengan memanggil setiap method tanpa meng-assert apapun yang bermakna. Mutation testing memodifikasi source code (mengubah operator, menghapus return, membalik boolean) dan memverifikasi bahwa setidaknya satu test gagal. Mutation yang bertahan mengungkap assertion yang terlalu longgar atau hilang sama sekali. Menjalankan php artisan test --mutate menghasilkan persentase mutation score bersamaan dengan coverage standar.

Untuk lebih banyak pertanyaan wawancara Laravel, bank soal SharpSkill mencakup authentication, pola service container, relasi Eloquent, dan arsitektur queue.

Pengujian HTTP Response dan JSON Assertion

Helper pengujian HTTP Laravel memverifikasi status code, header, struktur JSON, dan target redirect. Dikombinasikan dengan Pest, helper ini membentuk integration test yang ringkas.

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']);
    });
});

Blok describe mengelompokkan test terkait authentication. Setiap nama test mendeskripsikan perilaku yang diharapkan, bukan implementasi. Method assertJsonValidationErrors memeriksa bahwa field tertentu memiliki pesan error validasi.

Sumber

Yang Perlu Diingat tentang Laravel Testing dengan Pest 5

  • Pest 5 dengan Laravel 13 mengeliminasi boilerplate melalui API konfigurasi fluent, chain expect(), dan penemuan test otomatis
  • Mesin TIA hanya menjalankan ulang test yang terpengaruh, memangkas eksekusi suite besar dari menit menjadi detik tanpa mengorbankan akurasi coverage
  • Feature test sebaiknya membentuk inti dari test suite Laravel, dengan unit test dicadangkan untuk business logic terisolasi
  • Facade fake (Mail::fake(), Queue::fake()) menangani mocking level framework, sementara Mockery menangani dependensi pihak ketiga yang di-inject via container
  • Architecture test memberlakukan aturan struktural (tidak ada Eloquent di controller, tidak ada fungsi debug) tanpa overhead runtime
  • Mutation testing dengan --mutate menangkap assertion lemah yang terlewat oleh code coverage saja
  • Factory state dan assertion expect()->and() yang di-chain menjaga setup data test tetap mudah dibaca dan assertion spesifik
  • Untuk persiapan wawancara Laravel, memahami mock/fake/spy, perilaku RefreshDatabase, TIA, dan mutation testing menunjukkan kematangan testing melampaui coverage dasar

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Laravel?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 2 September 2026

Tag

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

Bagikan

Artikel terkait