Files
Pham-Tan-Thien da1bd48444 feat: decouple Laravel and CakePHP installers, remove SonarQube artifacts, and enforce complete unit testing standards
- Decouple framework installation: add install-laravel.sh and install-cakephp.sh, isolate skills and docs per framework, and auto-clean leftover artifacts
- Remove all SonarQube configs, templates, and references
- Enforce comprehensive test completeness: 100% public methods, edge cases, full branch coverage, exception paths, and state assertions
- Replace all placeholder assertions in PHPUnit and Pest templates with full realistic implementations
- Rename and update 10-comprehensive-test-coverage-guide.md
2026-09-17 14:25:02 +07:00

4.2 KiB

Component Guide: Queueable Jobs

Jobs xử lý các tác vụ nền không đồng bộ (Asynchronous Background Tasks) như gửi email hàng loạt, xử lý file nặng, đồng bộ dữ liệu.


1. Hai Mức Độ Kiểm Thử Cho Jobs

  1. Kiểm thử Dispatching (Job được đẩy vào hàng đợi đúng lúc không?):
    • Dùng Queue::fake() trong Service/Controller test.
    • Xác nhận Queue::assertPushed(MyJob::class).
  2. Kiểm thử Thực Thi (Unit Test trực tiếp method handle() của Job):
    • Đây chính là đối tượng của Unit Test Job.
    • Khởi tạo trực tiếp đối tượng $job = new ProcessPaymentJob(...).
    • Inject mock dependencies vào method handle().
    • Kiểm tra xem Job có thực thi logic và gọi service đúng không.
    • Kiểm tra method failed(\Throwable $exception) khi gặp sự cố.

2. Mã Mẫu Chuẩn: Job Unit Test

Job Nguồn: App\Jobs\SendWelcomeEmailJob

namespace App\Jobs;

use App\Contracts\MailerInterface;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendWelcomeEmailJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;

    public function __construct(
        public readonly User $user
    ) {}

    public function handle(MailerInterface $mailer): void
    {
        $mailer->sendWelcome($this->user->email, $this->user->name);
    }

    public function failed(?\Throwable $exception): void
    {
        \Illuminate\Support\Facades\Log::error("Failed to send welcome email to user {$this->user->id}");
    }
}

Test Chuẩn: PHPUnit 10+

namespace Tests\Unit\Jobs;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
use Mockery;
use App\Jobs\SendWelcomeEmailJob;
use App\Contracts\MailerInterface;
use App\Models\User;
use Illuminate\Support\Facades\Log;

class SendWelcomeEmailJobTest extends TestCase
{
    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    #[Test]
    public function it_sends_welcome_email_when_handled(): void
    {
        // Arrange
        $user = new User(['id' => 10, 'name' => 'Thien', 'email' => 'thien@example.com']);
        $mailerMock = Mockery::mock(MailerInterface::class);
        $mailerMock->shouldReceive('sendWelcome')
            ->once()
            ->with('thien@example.com', 'Thien');

        $job = new SendWelcomeEmailJob($user);

        // Act
        $job->handle($mailerMock);

        // Assert: Mockery tự động verify qua expectation ->once()
        $this->assertTrue(true);
    }

    #[Test]
    public function it_logs_error_when_job_fails(): void
    {
        // Arrange
        $user = new User(['id' => 10, 'name' => 'Thien', 'email' => 'thien@example.com']);
        $job = new SendWelcomeEmailJob($user);

        Log::shouldReceive('error')
            ->once()
            ->with('Failed to send welcome email to user 10');

        // Act
        $job->failed(new \RuntimeException('SMTP Connection Timeout'));
    }
}

3. Bẫy Nghiêm Trọng Cần Tránh Khi Test Jobs

❌ Bẫy Anti-Pattern: Chỉ dùng Reflection test hàm helper nhỏ

Nhiều AI và lập trình viên hay dùng ReflectionMethod để test các hàm helper nhỏ như buildRedisKey(), getFilePath() và bỏ qua hoàn toàn phương thức chính handle().

  • Hậu quả: Độ phủ mã nguồn thực tế của Job chỉ đạt 1% - 10%, vì 90% dòng code nghiệp vụ nằm trong handle() không hề được chạy!
  • Quy tắc vàng:
    1. BẮT BUỘC 100% phải test method handle().
    2. Mock toàn bộ dependencies của handle(): Storage (Storage::fake()), Database/Repository, Redis (Redis::shouldReceive()), Mailer.
    3. Phải có bài test cho các trường hợp rẽ nhánh trong handle(): khi dữ liệu rỗng, khi export thành công, khi import lỗi từng dòng.
    4. Phải có bài test gọi method failed(?Throwable $e) để kiểm tra việc ghi log hoặc bắn alert khi job thất bại.