# 01. Overview & Core Principles of Laravel Unit Testing Tài liệu này định nghĩa triết lý, nguyên tắc kiến trúc và tiêu chuẩn cốt lõi khi viết Unit Test cho mã nguồn Laravel. --- ## 1. Bản chất của Unit Test trong Laravel ### A. Định nghĩa Unit Test (Kiểm thử đơn vị) - **Đối tượng**: Kiểm thử một Class duy nhất, một Method duy nhất, hoặc một đoạn Business Logic độc lập. - **Phạm vi**: Hoàn toàn **cô lập (Isolated)**. Tất cả các thành phần ngoại vi (Database, Third-party API, File System, Queue, Mail, Event) đều phải được **giả lập (Mocked/Faked/Stubbed)**. - **Tốc độ**: Cực nhanh (dưới 10ms - 50ms cho mỗi test case). Toàn bộ bộ Unit Test của một hệ thống lớn phải chạy xong trong vài giây. ### B. Ranh giới giữa Unit Test và Feature/Integration Test Sự nhầm lẫn giữa Unit Test và Feature Test là nguyên nhân số 1 khiến test bị chậm và khó bảo trì: | Tiêu chí | Unit Test | Feature / Integration Test | | :--- | :--- | :--- | | **Đối tượng test** | 1 Class / Method cụ thể (Service, Action, Value Object, Rule,...) | Toàn bộ luồng nghiệp vụ (Endpoint, Controller, Middleware, DB) | | **Kế thừa TestCase** | `PHPUnit\Framework\TestCase` (thuần) hoặc `Tests\TestCase` (tối giản) | `Tests\TestCase` (boot toàn bộ Laravel Kernel) | | **Database** | **KHÔNG DÙNG** Database thật (Mock repository/model) | Có dùng DB (thường dùng `RefreshDatabase` với SQLite/MySQL) | | **HTTP Request** | Không dispatch HTTP request thật | Dùng `$this->getJson()`, `$this->postJson()` | | **External Service** | Mock bằng Mockery hoặc Anonymous Stub | Fake bằng `Http::fake()`, `Event::fake()` | | **Thời gian chạy** | Siêu nhanh (vài milliseconds) | Chậm hơn (vài chục đến hàng trăm milliseconds) | > ⚠️ **Quy tắc vàng**: Nếu một test cần chạy migration, chèn dữ liệu vào bảng, gọi HTTP endpoint qua router -> Đó là **Feature Test**, KHÔNG PHẢI Unit Test. --- ## 2. Quy chuẩn Cấu trúc: Mô hình AAA (Arrange - Act - Assert) Mọi bài test đơn vị đều phải tuân thủ nghiêm ngặt 3 giai đoạn: ```php #[Test] public function it_calculates_total_price_with_discount_successfully(): void { // 1. ARRANGE (Chuẩn bị dữ liệu đầu vào & thiết lập Mock) $discountServiceMock = Mockery::mock(DiscountServiceInterface::class); $discountServiceMock->shouldReceive('getPercentage') ->once() ->with('SUMMER2026') ->andReturn(20.0); $cartService = new CartService($discountServiceMock); $items = [ new CartItem(name: 'Item A', price: 100_000, quantity: 2), // 200,000 ]; // 2. ACT (Thực thi hành động cần test) $finalPrice = $cartService->calculateTotal($items, 'SUMMER2026'); // 3. ASSERT (Kiểm tra kết quả và xác thực side-effects) $this->assertSame(160_000.0, $finalPrice); } ``` - **Arrange**: Chuẩn bị đầy đủ input, khởi tạo đối tượng cần test, thiết lập kỳ vọng cho các dependency mock. - **Act**: Gọi chính xác 1 method của đối tượng cần test. Không gọi lồng nhiều hành động. - **Assert**: So sánh kết quả trả về với kỳ vọng; xác nhận mock method được gọi đúng số lần và đúng tham số. --- ## 3. Các Nguyên tắc Cốt lõi (Core Principles) ### 1. Nguyên tắc Đơn nhiệm (Single Concept per Test) Mỗi test method chỉ kiểm tra **một khía cạnh duy nhất** của logic: - 1 test cho trường hợp thành công (Happy path). - 1 test cho trường hợp dữ liệu rỗng/null. - 1 test cho trường hợp văng Exception khi đầu vào sai. ❌ Không gộp tất cả các kịch bản if/else vào chung một test method dài 100 dòng. ### 2. Tính Độc lập & Không Phụ thuộc Thứ tự (Independent & Isolated) - Test case A chạy trước hay test case B chạy trước không được làm ảnh hưởng kết quả của nhau. - Không chia sẻ trạng thái biến toàn cục (global state) hoặc static variable mà không dọn dẹp sau mỗi test (`tearDown()`). ### 3. Nguyên tắc FIRST trong Unit Testing - **F - Fast**: Chạy trong chớp mắt. - **I - Isolated / Independent**: Không phụ thuộc môi trường mạng, database bên ngoài. - **R - Repeatable**: Chạy 100 lần trên máy local hay CI/CD đều cho ra kết quả giống nhau. - **S - Self-validating**: Test tự xác nhận Pass hoặc Fail qua Assertions, không cần người đọc log để đoán. - **T - Thorough / Timely**: Bao phủ từ Happy Path cho đến các trường hợp biên khắc nghiệt (Edge cases).