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>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# 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);
|
||||
```
|
||||
Reference in New Issue
Block a user