Files
agent-unit-testing/docs/07-troubleshooting-and-debugging.md
T
Pham-Tan-Thien 2045c856e9 feat: complete Smartgift AI Laravel unit testing toolkit
- Add 10 core testing guides (setup, AAA principles, PHPUnit/Pest, mocking, anti-patterns, PHP 8.4+, debugging, data factories, advanced edge cases)
- Add 13 component testing guides covering all Laravel layers
- Add standard templates for PHPUnit 10/11 and Pest v2/v3 including ArchitectureTest
- Add Docker smart test runner, dependency installer, and CI/CD templates (GitHub Actions, GitLab CI)
- Add 4 Claude Code skills (laravel-test-setup, laravel-unit-test with --all mode, laravel-test-review, laravel-mock-helper)
- Add single unified installer script (sg-install-unit-testing.sh) with global support, CLI runners, and 1-click updater (sg-test-update)
- Add comprehensive documentation (README.md, USAGE-GUIDE.md)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 16:41:37 +07:00

124 lines
6.0 KiB
Markdown

# 07. Troubleshooting & Debugging Guide for Laravel Unit Tests
Cẩm nang chẩn đoán và khắc phục 10 lỗi phổ biến nhất khi viết và chạy Unit Test trong các dự án Laravel.
---
## 1. Lỗi: `Mockery\Exceptions\InvalidCountException: Method ... was expected to be called 1 times but called 0 times`
- **Nguyên nhân**:
- Bạn khai báo `->once()` cho một method mock, nhưng luồng code trong hàm thực tế bị rẽ nhánh sớm (early return) hoặc ném exception trước khi chạm tới method đó.
- Tên method trong `shouldReceive('tên_hàm')` bị sai chính tả.
- **Cách khắc phục**:
1. Kiểm tra điều kiện `if/else` trước lệnh gọi: Đảm bảo dữ liệu đầu vào trong `// Arrange` thỏa mãn để code đi vào nhánh gọi mock.
2. Kiểm tra log hoặc dùng `$this->withoutExceptionHandling()` (nếu trong Feature test) để xem có exception nào bị nuốt không.
---
## 2. Lỗi: `BadMethodCallException: Method Mockery_...::someMethod() does not exist on this mock object`
- **Nguyên nhân**: Code nguồn gọi phương thức `someMethod()`, nhưng bạn chưa định nghĩa nó trong mock (`shouldReceive('someMethod')`).
- **Cách khắc phục**:
- Khai báo bổ sung kỳ vọng cho method đó, hoặc nếu method đó không ảnh hưởng tới kết quả bài test, dùng `->shouldIgnoreMissing()` hoặc tạo `Spy` thay vì `Mock`:
```php
$loggerMock = Mockery::spy(LoggerInterface::class);
```
---
## 3. Lỗi: `Target [App\Contracts\PaymentInterface] is not instantiable while building [App\Services\CheckoutService]`
- **Nguyên nhân**: Code nguồn cố gắng resolve Interface từ Service Container nhưng Interface đó chưa được bind trong `AppServiceProvider` trong môi trường test.
- **Cách khắc phục**:
- Trong Unit Test: **Khởi tạo trực tiếp bằng từ khóa `new`** và truyền mock vào constructor thay vì gọi `app(CheckoutService::class)`:
```php
// ĐÚNG CHO UNIT TEST:
$service = new CheckoutService($mockPayment);
// NẾU CẦN RESOLVE TỪ CONTAINER:
$this->app->instance(PaymentInterface::class, $mockPayment);
$service = $this->app->make(CheckoutService::class);
```
---
## 4. Lỗi: `SQLSTATE[HY000]: General error: 1 no such table: users`
- **Nguyên nhân**: Test đang chạy trên SQLite `:memory:` nhưng chưa chạy migration, hoặc trong Unit test bạn vô tình gọi Eloquent Model thật mà không dùng trait `RefreshDatabase`.
- **Cách khắc phục**:
1. **Nếu là Unit Test thuần**: Bạn không nên chạm vào DB! Hãy mock Repository hoặc khởi tạo in-memory model `$user = new User(['id' => 1])`.
2. **Nếu là Repository/Query Test bắt buộc cần DB**: Thêm trait `use RefreshDatabase;` vào đầu class test.
---
## 5. Lỗi: `SQLSTATE[HY000]: General error: 1 no such function: JSON_CONTAINS / DATE_FORMAT`
- **Nguyên nhân**: Code nguồn sử dụng hàm SQL đặc thù của MySQL, nhưng môi trường test chạy trên SQLite In-Memory.
- **Cách khắc phục**:
- Đăng ký hàm giả lập trong `setUp()`:
```php
if (DB::connection() instanceof \Illuminate\Database\SQLiteConnection) {
DB::connection()->getPdo()->sqliteCreateFunction('JSON_CONTAINS', function ($json, $val) {
$data = json_decode($json, true) ?? [];
return in_array(trim($val, '"'), $data) ? 1 : 0;
}, 2);
}
```
- Hoặc chuyển kết nối test trong `phpunit.xml` sang một database MySQL test độc lập trên Docker.
---
## 6. Lỗi: `Illuminate\Database\QueryException: Integrity constraint violation: 19 FOREIGN KEY constraint failed`
- **Nguyên nhân**: SQLite bật kiểm tra khóa ngoại và bản ghi cha không tồn tại.
- **Cách khắc phục**:
- Tạm thời tắt kiểm tra foreign key trong test setup:
```php
use Illuminate\Support\Facades\Schema;
protected function setUp(): void
{
parent::setUp();
Schema::disableForeignKeyConstraints();
}
```
- Hoặc tạo các Model quan hệ bằng Factory: `User::factory()->has(Post::factory())->create();`.
---
## 7. Lỗi: Flaky Test (Chạy riêng lẻ thì Pass, chạy chung cả bộ test thì Fail)
- **Nguyên nhân**: **Rò rỉ trạng thái (State Leakage)** giữa các bài test:
- Dùng `Carbon::setTestNow()` nhưng quên reset.
- Sử dụng static variable hoặc singleton service lưu cache trong memory.
- Mock Facade tĩnh chưa được giải phóng.
- **Cách khắc phục**:
- Đảm bảo trong `tearDown()` có gọi:
```php
protected function tearDown(): void
{
Carbon::setTestNow(); // Reset thời gian thực
Mockery::close();
parent::tearDown();
}
```
---
## 8. Lỗi: `Error: Typed property ... must not be accessed before initialization`
- **Nguyên nhân**: Thuộc tính khai báo type (ví dụ `private UserService $service;`) nhưng trong method `setUp()` bị sót logic gán giá trị, hoặc test method chạy trước khi `setUp()` hoàn tất.
- **Cách khắc phục**: Kiểm tra phương thức `setUp()` và đảm bảo mọi typed property đều được khởi tạo instance trước khi method test chạy.
---
## 9. Lỗi: `Class "App\Http\Controllers\Controller" not found`
- **Nguyên nhân**: Laravel 11 đã loại bỏ base `Controller.php` mặc định nếu dự án tạo mới tối giản, hoặc namespace bị sai lệch.
- **Cách khắc phục**: Kiểm tra class controller đích kế thừa class nào để import chính xác.
---
## 10. Lỗi: `Session store not set on request`
- **Nguyên nhân**: Code nguồn gọi `request()->session()` hoặc `$request->session()->get(...)` trong Unit test khi chưa có middleware session.
- **Cách khắc phục**:
- Gắn session giả lập vào Request:
```php
use Illuminate\Session\ArraySessionHandler;
use Illuminate\Session\Store;
$session = new Store('test_session', new ArraySessionHandler(10));
$request->setLaravelSession($session);
```