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,100 @@
|
||||
# Component Guide: Repositories & Custom Query Builders
|
||||
|
||||
Kiểm thử tầng Repository, Custom Query Builder và Eloquent Scopes đòi hỏi chiến lược đặc thù vì chúng tương tác trực tiếp với cơ sở dữ liệu.
|
||||
|
||||
---
|
||||
|
||||
## 1. Bản Chất & Chiến Lược Kiểm Thử
|
||||
|
||||
### Tại sao KHÔNG nên Mock Eloquent Query Builder?
|
||||
Eloquent Builder có hàng trăm phương thức liên kết chuỗi (`where`, `orWhere`, `whereHas`, `orderBy`, `limit`). Việc mock toàn bộ chuỗi này vừa cực kỳ tốn công sức vừa tạo ra các bài test **rất dễ vỡ (brittle)**.
|
||||
|
||||
### Giải Pháp Chuẩn: SQLite In-Memory Database
|
||||
Đối với Repository và Query Builder, hãy sử dụng **SQLite In-Memory** (`:memory:`):
|
||||
- Chạy cực nhanh (dưới 30ms).
|
||||
- Sử dụng trait `Illuminate\Foundation\Testing\RefreshDatabase`.
|
||||
- Cho phép thực thi câu lệnh SQL thật để đảm bảo query trả về đúng dữ liệu, đúng điều kiện lọc và đúng sắp xếp.
|
||||
|
||||
---
|
||||
|
||||
## 2. Mã Mẫu Chuẩn: Eloquent Scope & Repository
|
||||
|
||||
### Class Nguồn: `App\Repositories\UserRepository`
|
||||
```php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class UserRepository
|
||||
{
|
||||
public function getActiveUsersOlderThan(int $age): Collection
|
||||
{
|
||||
return User::query()
|
||||
->where('is_active', true)
|
||||
->where('age', '>', $age)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Chuẩn: PHPUnit 10+ (với SQLite In-Memory)
|
||||
```php
|
||||
namespace Tests\Unit\Repositories;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UserRepository;
|
||||
|
||||
class UserRepositoryTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase; // Tự động migrate trên SQLite in-memory
|
||||
|
||||
private UserRepository $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->repository = new UserRepository();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_only_active_users_older_than_specified_age_ordered_by_name(): void
|
||||
{
|
||||
// Arrange: Tạo dữ liệu kiểm thử
|
||||
// Thỏa mãn cả 2 điều kiện
|
||||
$validUserB = User::factory()->create(['name' => 'Binh', 'is_active' => true, 'age' => 25]);
|
||||
$validUserA = User::factory()->create(['name' => 'An', 'is_active' => true, 'age' => 30]);
|
||||
|
||||
// Không thỏa mãn is_active
|
||||
User::factory()->create(['name' => 'Cuong', 'is_active' => false, 'age' => 28]);
|
||||
|
||||
// Không thỏa mãn age (<= 20)
|
||||
User::factory()->create(['name' => 'Dung', 'is_active' => true, 'age' => 18]);
|
||||
|
||||
// Act
|
||||
$results = $this->repository->getActiveUsersOlderThan(20);
|
||||
|
||||
// Assert
|
||||
$this->assertCount(2, $results);
|
||||
$this->assertSame($validUserA->id, $results->first()->id); // Kiểm tra thứ tự A trước B
|
||||
$this->assertSame($validUserB->id, $results->last()->id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_empty_collection_when_no_user_matches(): void
|
||||
{
|
||||
// Arrange
|
||||
User::factory()->create(['is_active' => false, 'age' => 50]);
|
||||
|
||||
// Act
|
||||
$results = $this->repository->getActiveUsersOlderThan(40);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($results->isEmpty());
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user