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