# 09. Các Tình Huống Kỹ Thuật Đặc Biệt (Advanced Edge Cases) Tài liệu hướng dẫn xử lý 7 tình huống kỹ thuật phức tạp thường gặp trong các dự án Laravel thực tế mà lập trình viên và AI hay bị lúng túng. --- ## 1. Mock `DB::transaction()` Trong Unit Test (Không Cần Database Thật) Nhiều Service bọc logic thanh toán/tạo đơn trong `DB::transaction(function () { ... })`. Khi viết Unit Test cô lập (không kết nối DB), nếu không mock lệnh này, code sẽ văng lỗi connection. ### ✅ Giải pháp chuẩn: Sử dụng `DB::shouldReceive('transaction')` và thực thi ngay closure truyền vào: ```php use Illuminate\Support\Facades\DB; #[Test] public function it_executes_logic_inside_database_transaction(): void { // Giả lập DB::transaction để chạy trực tiếp callback bên trong DB::shouldReceive('transaction') ->once() ->andReturnUsing(function (callable $callback) { return $callback(); }); // Act $result = $this->orderService->createOrder($orderDTO); // Assert $this->assertNotNull($result); } ``` --- ## 2. Kiểm Thử Trait Trong Sự Cô Lập (Testing Traits in Isolation) Khi logic nghiệp vụ nằm trong một Trait (ví dụ: `GeneratesInvoiceCodeTrait`, `HasSlugTrait`), làm sao để test mà không cần khởi tạo một Model hoặc Service cụ thể? ### ✅ Giải pháp chuẩn: Dùng Anonymous Class ```php #[Test] public function it_generates_unique_invoice_code_from_trait(): void { // Tạo một anonymous class sử dụng Trait cần test $instance = new class { use GeneratesInvoiceCodeTrait; // Bổ sung các method/property trừu tượng nếu trait yêu cầu public function prefix(): string { return 'INV'; } }; $code = $instance->generateInvoiceCode(123); $this->assertMatchesRegularExpression('/^INV-123-[A-Z0-9]{4}$/', $code); } ``` --- ## 3. Thao Tác Cấu Hình (`config()`) & Biến Môi Trường Trong Test Nhiều class đọc cấu hình qua `config('services.stripe.secret')`. ### ✅ Quy tắc chuẩn: - **KHÔNG BAO GIỜ** sửa file `.env` thật trong lúc test. - Sử dụng `Config::set()` trực tiếp trong phần `// Arrange`: ```php use Illuminate\Support\Facades\Config; #[Test] public function it_uses_configured_vat_rate(): void { // Gán tạm thời cấu hình cho bài test này Config::set('finance.vat_rate', 0.08); // 8% $tax = $this->taxService->calculate(100_000); $this->assertSame(8_000.0, $tax); } ``` --- ## 4. Kiểm Thử Upload File & Multi-part Request (`UploadedFile::fake()`) Kiểm thử các service xử lý file ảnh, tài liệu PDF, file CSV mà không cần file thật trên ổ cứng: ```php use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; #[Test] public function it_uploads_and_validates_pdf_invoice(): void { Storage::fake('documents'); // Tạo file PDF giả lập trong bộ nhớ $fakePdf = UploadedFile::fake()->create('invoice.pdf', 500, 'application/pdf'); $uploadedPath = $this->documentService->storeInvoice($fakePdf); Storage::disk('documents')->assertExists($uploadedPath); } #[Test] public function it_rejects_file_exceeding_max_size(): void { // Giả lập file ảnh vượt quá 10MB (11,000 KB) $heavyImage = UploadedFile::fake()->image('huge.jpg')->size(11_000); $this->expectException(FileTooLargeException::class); $this->avatarService->upload($heavyImage); } ``` --- ## 5. Kiểm Thử Lỗi Serialization Của Queueable Jobs (`SerializesModels`) Trong Laravel, khi truyền một Eloquent Model vào Job, Laravel sẽ serialize model bằng ID của nó. Nếu truyền một Model chưa được lưu vào DB (ID = null), Job sẽ crash trong worker thực tế. ### ✅ Cách kiểm tra độ an toàn của Job Payload: ```php #[Test] public function it_can_be_serialized_and_unserialized_safely(): void { $user = User::factory()->make(['id' => 101]); $job = new SendWelcomeEmailJob($user); // Thử serialize và unserialize giống hệt cơ chế của Queue Worker $serialized = serialize($job); $unserializedJob = unserialize($serialized); $this->assertInstanceOf(SendWelcomeEmailJob::class, $unserializedJob); $this->assertSame(101, $unserializedJob->user->id); } ``` --- ## 6. Xử Lý Giả Lập Session & Authentication Trong Unit Test Thuần Khi class nguồn gọi `auth()->user()`, `auth()->id()` hoặc `session()->get()`: ```php use Illuminate\Support\Facades\Auth; use App\Models\User; #[Test] public function it_retrieves_data_for_authenticated_user(): void { // Arrange: Giả lập user đã đăng nhập vào Auth Facade $mockUser = new User(['id' => 99, 'name' => 'Thien Phan']); Auth::shouldReceive('user')->andReturn($mockUser); Auth::shouldReceive('id')->andReturn(99); Auth::shouldReceive('check')->andReturnTrue(); // Act $result = $this->userProfileService->getCurrentUserProfile(); // Assert $this->assertSame('Thien Phan', $result->name); } ``` --- ## 7. Kiểm Thử Sự Kiện Đa Kênh & Tránh Cascading Events Khi một hành động kích hoạt Event, và Event đó lại kích hoạt nhiều Listener khác có thể ghi dữ liệu hoặc gửi mail: ```php use Illuminate\Support\Facades\Event; use App\Events\UserRegisteredEvent; #[Test] public function it_dispatches_registered_event_without_firing_listeners(): void { // Chặn tất cả Listener chạy ngầm, chỉ xác nhận Event được bắn ra Event::fake([ UserRegisteredEvent::class ]); $this->registrationService->register($userData); // Xác nhận event được dispatch kèm payload chuẩn Event::assertDispatched(UserRegisteredEvent::class, function ($event) use ($userData) { return $event->userEmail === $userData['email']; }); } ```