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>
129 lines
3.7 KiB
Markdown
129 lines
3.7 KiB
Markdown
# 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);
|
|
}
|
|
}
|
|
```
|