Files
agent-unit-testing/docs/components/04-repositories-and-queries.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

101 lines
3.3 KiB
Markdown

# 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());
}
}
```