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

3.3 KiB

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

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)

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