2045c856e9
- 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>
101 lines
3.3 KiB
Markdown
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());
|
|
}
|
|
}
|
|
```
|