Files
agent-unit-testing/docs/components/06-jobs-and-queues.md
T
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

125 lines
4.2 KiB
Markdown

# 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`
```php
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+
```php
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.