Files
agent-unit-testing/docs/components/05-models.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.7 KiB

Component Guide: Eloquent Models & Custom Casts

Eloquent Model thường chứa logic nghiệp vụ mức trường dữ liệu như Accessors, Mutators, Custom Casts và quan hệ (Relationships).


1. Bản Chất Kiểm Thử Model

  • Cần test:
    • Accessors (Casting output): Ví dụ tính toán full_name, định dạng tiền tệ, mask số thẻ tín dụng.
    • Mutators (Casting input): Chuẩn hóa dữ liệu trước khi lưu (viết hoa, mã hóa mật khẩu, slug).
    • Custom Casts: Lớp kế thừa CastsAttributes chuyển đổi JSON/Object <-> Database column.
    • Model Helper Methods: Các phương thức logic nội bộ (ví dụ: $user->isVip(), $order->canBeCancelled()).
  • Không cần test:
    • Các tính năng có sẵn của Laravel như hasMany(), belongsTo() trừ khi muốn xác nhận foreign key cấu hình đúng.

2. Mã Mẫu Chuẩn: Model Accessor & Logic Method (Không Cần DB)

Model Nguồn: App\Models\Order

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $fillable = ['total_amount', 'status', 'created_at'];

    public function getFormattedTotalAttribute(): string
    {
        return number_format($this->total_amount, 0, ',', '.') . ' ₫';
    }

    public function canBeCancelled(): bool
    {
        return in_array($this->status, ['pending', 'processing'], true);
    }
}

Test Chuẩn: PHPUnit 10+ (Thuần In-Memory - Không Cần Database)

namespace Tests\Unit\Models;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
use App\Models\Order;

class OrderTest extends TestCase
{
    #[Test]
    public function it_formats_total_amount_as_vietnam_currency(): void
    {
        // Arrange: Khởi tạo model thuần trong bộ nhớ, không gọi ->save()
        $order = new Order(['total_amount' => 1500000]);

        // Act & Assert
        $this->assertSame('1.500.000 ₫', $order->formatted_total);
    }

    #[Test]
    #[DataProvider('cancellationStatusProvider')]
    public function it_determines_if_order_can_be_cancelled(string $status, bool $expectedResult): void
    {
        // Arrange
        $order = new Order(['status' => $status]);

        // Act & Assert
        $this->assertSame($expectedResult, $order->canBeCancelled());
    }

    public static function cancellationStatusProvider(): array
    {
        return [
            'pending status'    => ['pending', true],
            'processing status' => ['processing', true],
            'shipped status'    => ['shipped', false],
            'completed status'  => ['completed', false],
            'cancelled status'  => ['cancelled', false],
        ];
    }
}

3. Mã Mẫu Chuẩn: Custom Cast Class

namespace Tests\Unit\Casts;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
use App\Casts\MoneyCast;
use App\ValueObjects\Money;
use App\Models\Order;

class MoneyCastTest extends TestCase
{
    #[Test]
    public function it_casts_integer_cents_from_database_to_money_value_object(): void
    {
        $cast = new MoneyCast();
        $order = new Order();

        // Giả lập đọc từ database (100,000 VND)
        $money = $cast->get($order, 'amount', 100000, []);

        $this->assertInstanceOf(Money::class, $money);
        $this->assertSame(100000, $money->getAmount());
    }

    #[Test]
    public function it_casts_money_object_to_integer_for_database_storage(): void
    {
        $cast = new MoneyCast();
        $order = new Order();

        $money = new Money(50000);
        $storedValue = $cast->set($order, 'amount', $money, []);

        $this->assertSame(50000, $storedValue);
    }
}