Laravel Testing với Pest 5 năm 2026: TIA, Mocking và Câu Hỏi Phỏng Vấn

Nắm vững các phương pháp kiểm thử Laravel với Pest 5, Test Impact Analysis, Mockery, facade fake và architecture test. Bao gồm unit test, feature test, chiến lược mocking và câu hỏi phỏng vấn phổ biến cho developer Laravel.

Huong dan kiem thu Laravel voi Pest bao gom mocking, architecture test va cau hoi phong van ky thuat 2026

Các phương pháp kiểm thử Laravel đã thay đổi đáng kể với Pest 5, công cụ TIA (Test Impact Analysis), và PHPUnit 13. Laravel 13 được tích hợp sẵn hỗ trợ Pest, mang lại trải nghiệm kiểm thử biểu cảm và ngắn gọn hơn so với PHPUnit thuần túy. Hướng dẫn này đề cập đến các pattern quan trọng cho cả ứng dụng production và phỏng vấn kỹ thuật.

Pest 5 Là Chuẩn Mực

Pest 5 (xây dựng trên PHPUnit 13) là framework kiểm thử mặc định cho Laravel 13. Framework này yêu cầu PHP 8.4+ và giới thiệu công cụ TIA, xác minh AI agent tích hợp, tích hợp PHPStan, và time-balanced sharding mặc định.

Cấu Hình Pest 5 Trong Dự Án Laravel 13

Mọi ứng dụng Laravel 13 mới đều tự động scaffold Pest. Với các dự án hiện có, việc di chuyển từ Pest 4 chỉ mất vài phút vì điều cần xem xét chính là khả năng tương thích PHPUnit 13. Cấu hình nằm trong tests/Pest.php, nơi các trait và helper method toàn cục được đăng ký.

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

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

Một file duy nhất này thay thế trait CreatesApplication cũ và kế thừa base test class. Trait RefreshDatabase bọc mỗi test trong transaction database, tự động rollback các thay đổi.

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() của Pest kết hợp tự nhiên với các assertion của PHPUnit. Lệnh gọi assertJsonStructure xác thực cấu trúc response, trong khi expect()->toBeTrue() xác nhận trạng thái database. Cả hai phong cách cùng tồn tại mà không xung đột.

Test Impact Analysis: Tính Năng Nổi Bật Của Pest 5

Công cụ TIA là yếu tố giúp Pest 5 mang tính chuyển đổi cho các codebase lớn. Trong lần chạy đầu tiên, Pest ghi lại test nào truy cập file nào. Mỗi lần chạy tiếp theo chỉ thực thi các test bị ảnh hưởng bởi thay đổi và phát lại kết quả đã cache cho phần còn lại.

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

# Subsequent runs: only affected tests execute
php artisan test

Test suite của Laravel Cloud, chứa hơn 19.000 test, giảm từ khoảng ba phút xuống năm giây khi bật TIA. Công cụ này hiểu không chỉ file PHP: nó phát hiện thay đổi trong migration, template Blade, và các component JavaScript dùng chung.

TIA yêu cầu coverage driver (PCOV hoặc Xdebug) để ghi baseline. Với các pipeline CI, các mapping đã cache được lưu giữa các lần chạy, làm cho hiệu quả tăng dần theo thời gian.

Unit Test và Feature Test Trong Laravel

Sự khác biệt giữa unit test và feature test trong Laravel quyết định phần nào của framework được khởi động. Unit test chạy mà không cần container ứng dụng, giúp chúng nhanh hơn nhưng giới hạn ở logic thuần túy. Feature test khởi động toàn bộ ứng dụng, cho phép gọi HTTP, truy vấn database và resolve 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 nhắm vào các class độc lập không có dependency bên ngoài. Khối describe nhóm các assertion liên quan, và Pest 5 hỗ trợ khối describe lồng nhau cho các phân cấp test phức tạp.

Feature test nên chiếm đa số trong test suite Laravel. Chúng phát hiện các bug tích hợp mà unit test bỏ lỡ, như middleware route không đúng, quy tắc validation thiếu, hoặc quan hệ Eloquent bị hỏng. Quy tắc chung: nếu code chạm vào database, tầng HTTP, hoặc facade, hãy viết feature test.

Chiến Lược Mocking Với Facade và Mockery

Mocking trong kiểm thử Laravel cô lập code đang test khỏi các dependency bên ngoài. Các facade Laravel cung cấp implementation fake tích hợp cho queue, event, notification, mail và storage. Mockery xử lý phần còn lại.

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 chặn các lệnh gọi ở tầng framework, ngăn email thực sự được gửi hoặc job được dispatch. Các assertion dựa trên closure xác minh dữ liệu chính xác được truyền cho từng component.

Mock Ở Ranh Giới Hệ Thống

Tránh mock các class domain nội bộ. Mock ở ranh giới: API bên thứ ba, mail, queue, filesystem. Mock quá mức làm test dễ vỡ và gắn chặt với chi tiết implementation.

Với các dependency không phải facade, inject qua constructor và sử dụng 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 với $this->app->instance() thay thế implementation thực trong suốt thời gian test. Mockery xác minh rằng charge được gọi đúng một lần với các đối số mong đợi.

Architecture Testing Với Preset Pest

Pest 5 bao gồm architecture testing để thực thi các quy tắc cấu trúc trong toàn bộ codebase. Các test này chạy dựa trên AST, không phải runtime, giúp chúng cực kỳ nhanh.

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

Quy tắc đầu tiên ngăn controller truy vấn database trực tiếp, thực thi pattern service layer. Quy tắc thứ hai đảm bảo service không thể được extend, giảm độ phức tạp kế thừa. Quy tắc thứ ba phát hiện các statement debug còn sót trước khi chúng đến production.

Các preset dành riêng cho Laravel cũng có sẵn:

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

Ba dòng này thực thi hàng chục quy tắc: model extend đúng base class, controller không chứa business logic, không có hàm không an toàn như eval() hoặc md5() để hash, và các quy ước PHP chuẩn được tuân thủ.

Sẵn sàng chinh phục phỏng vấn Laravel?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Mutation Testing Để Xác Minh Chất Lượng Test

Code coverage đo các dòng nào được thực thi trong test. Mutation testing đi xa hơn: nó sửa đổi source code và kiểm tra xem test có phát hiện thay đổi không. Nếu một mutation sống sót, test suite có lỗ hổng.

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

Pest 5 giới thiệu các mutation như thay đổi > thành >=, xóa statement return, và đảo điều kiện boolean. Mutation score dưới 80% thường cho thấy test xác minh output mà không kiểm tra các trường hợp biên.

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 đầu tiên vẫn pass ngay cả khi logic tính toán hoàn toàn sai, miễn là nó trả về float. Test thứ hai fail ngay lập tức khi mutation thay đổi công thức. Các assertion cụ thể tạo ra mutation score cao hơn.

Pattern Kiểm Thử Database và Factory

Model factory Laravel tạo dữ liệu test thực tế mà không cần xây dựng array thủ công. Pest 5 kết hợp với factory tạo ra setup dữ liệu dễ đọc và dễ bảo trì.

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() đặt giá trị mặc định cho bài viết chưa xuất bản. Các assertion expect() được chain với ->and() đọc như ngôn ngữ tự nhiên và fail với thông báo mô tả rõ ràng.

Parallel Testing và Sharding

Chạy php artisan test --parallel để thực thi test trên nhiều process. Laravel tự động tạo database test riêng cho mỗi process, ngăn xung đột dữ liệu. Time-balanced sharding của Pest 5 phân phối test theo thời gian thực thi thực tế thay vì số lượng, đảm bảo mọi CI shard kết thúc cùng lúc.

Câu Hỏi Phỏng Vấn Kỹ Thuật Laravel Testing

Các buổi phỏng vấn kỹ thuật cho vị trí Laravel thường xuyên kiểm tra kiến thức testing. Dưới đây là các câu hỏi xuất hiện nhiều nhất, cùng với những điểm mà câu trả lời tốt cần đề cập.

Sự khác biệt giữa fake(), mock() và spy() trong kiểm thử Laravel là gì?

Facade fake() thay thế toàn bộ facade bằng implementation in-memory (Mail::fake, Queue::fake). mock() qua Mockery đặt kỳ vọng trước khi thực thi và fail nếu không đáp ứng. spy() ghi lại các tương tác và cho phép assertion sau khi thực thi, mà không đặt kỳ vọng trước. Lựa chọn phụ thuộc vào việc test cần xác minh hành vi (mock), ghi lại tương tác (spy), hay ngăn side effect (fake).

RefreshDatabase khác DatabaseTransactions như thế nào?

RefreshDatabase chạy migration một lần và bọc mỗi test trong transaction. DatabaseTransactions giả định database đã có schema đúng và chỉ bọc test trong transaction. RefreshDatabase an toàn hơn cho pipeline CI nơi database có thể chưa tồn tại. DatabaseTransactions nhanh hơn khi schema được đảm bảo là hiện tại.

Khi nào nên ưu tiên feature test hơn unit test?

Feature test nên bao phủ bất kỳ code nào tương tác với tầng HTTP, database, hoặc service Laravel. Unit test được dành cho các pure function và value object không có dependency framework. Trong ứng dụng Laravel điển hình, feature test nhiều hơn unit test với tỷ lệ khoảng 3:1 hoặc cao hơn. Điều này phản ánh thực tế rằng hầu hết code Laravel vốn đã tích hợp với framework.

Test Impact Analysis cải thiện hiệu suất CI như thế nào?

TIA ghi lại test nào chạm vào file source nào trong lần chạy đầu tiên. Trong các lần chạy tiếp theo, Pest chỉ thực thi các test bị ảnh hưởng bởi file đã thay đổi và phát lại kết quả đã cache cho các test không thay đổi. Các suite lớn mất nhiều phút có thể hoàn thành trong vài giây. Đánh đổi: TIA yêu cầu coverage driver (PCOV hoặc Xdebug) để xây dựng bản đồ dependency, và lần chạy đầu tiên lâu hơn bình thường.

Mutation testing cải thiện chất lượng test như thế nào so với code coverage?

Code coverage đo các đường thực thi. Một test có thể đạt coverage 100% bằng cách gọi mọi method mà không assert gì có ý nghĩa. Mutation testing sửa đổi source code (thay đổi operator, xóa return, đảo boolean) và xác minh rằng ít nhất một test fail. Các mutation sống sót cho thấy assertion quá lỏng hoặc thiếu hoàn toàn. Chạy php artisan test --mutate tạo ra phần trăm mutation score cùng với coverage tiêu chuẩn.

Để tìm thêm câu hỏi phỏng vấn Laravel, ngân hàng câu hỏi SharpSkill bao gồm authentication, pattern service container, quan hệ Eloquent, và kiến trúc queue.

Kiểm Thử HTTP Response và JSON Assertion

Các helper kiểm thử HTTP của Laravel xác minh status code, header, cấu trúc JSON và target redirect. Kết hợp với Pest, chúng tạo thành các integration test ngắn gọn.

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

Khối describe nhóm các test liên quan đến authentication. Mỗi tên test mô tả hành vi mong đợi, không phải implementation. Method assertJsonValidationErrors kiểm tra rằng các field cụ thể có thông báo lỗi validation.

Nguồn Tham Khảo

Những Điều Cần Nhớ Về Laravel Testing Với Pest 5

  • Pest 5 với Laravel 13 loại bỏ boilerplate thông qua API cấu hình fluent, chain expect(), và tự động phát hiện test
  • Công cụ TIA chỉ chạy lại các test bị ảnh hưởng, giảm thời gian thực thi suite lớn từ phút xuống giây mà không hy sinh độ chính xác coverage
  • Feature test nên tạo thành cốt lõi của test suite Laravel, với unit test được dành cho business logic độc lập
  • Facade fake (Mail::fake(), Queue::fake()) xử lý mocking ở tầng framework, trong khi Mockery xử lý dependency bên thứ ba được inject qua container
  • Architecture test thực thi các quy tắc cấu trúc (không có Eloquent trong controller, không có hàm debug) mà không có overhead runtime
  • Mutation testing với --mutate phát hiện các assertion yếu mà code coverage đơn thuần bỏ lỡ
  • Factory state và các assertion expect()->and() được chain giữ cho setup dữ liệu test dễ đọc và các assertion cụ thể
  • Để chuẩn bị phỏng vấn Laravel, hiểu mock/fake/spy, hành vi RefreshDatabase, TIA, và mutation testing cho thấy sự trưởng thành về testing vượt xa coverage cơ bản

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thử thách hôm nay

Bạn có tìm ra lỗi trong Laravel không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 2 tháng 9, 2026

Thẻ

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

Chia sẻ

Bài viết liên quan