# 🧩 CakePHP Components Unit Testing Reference > **Hướng dẫn chi tiết cách viết test cho từng tầng thành phần trong CakePHP (Table, Entity, Controller, Service, Command, Component, Middleware, Mailer, Form, Behavior).** --- ## 1. Table Classes (`src/Model/Table/*Table.php`) - **Base Class**: `Cake\TestSuite\TestCase` - **Các điểm cần test**: - `validationDefault()`: Đầu vào hợp lệ, thiếu trường bắt buộc, sai format (email, regex, số âm). - `buildRules()`: Quy tắc toàn vẹn dữ liệu (`isUnique`, `existsIn`). - Custom Finders (`findPublished()`, `findAuth()`): Truy vấn trả về đúng bản ghi mong muốn. - Custom methods: Xử lý nghiệp vụ lưu dữ liệu. ```php namespace App\Test\TestCase\Model\Table; use App\Model\Table\ArticlesTable; use Cake\TestSuite\TestCase; class ArticlesTableTest extends TestCase { protected ArticlesTable $Articles; protected array $fixtures = ['app.Articles']; protected function setUp(): void { parent::setUp(); $this->Articles = $this->getTableLocator()->get('Articles'); } public function testFindPublished(): void { $query = $this->Articles->find('published'); $this->assertInstanceOf(\Cake\ORM\Query::class, $query); $result = $query->toArray(); $this->assertNotEmpty($result); foreach ($result as $article) { $this->assertTrue($article->published); } } } ``` --- ## 2. Entity Classes (`src/Model/Entity/*.php`) - **Base Class**: `Cake\TestSuite\TestCase` - **Các điểm cần test**: - `_setPassword()` (Hashing logic). - Virtual Fields (`_getFullName()`). - `$_accessible` properties (Bảo vệ Mass Assignment). ```php namespace App\Test\TestCase\Model\Entity; use App\Model\Entity\User; use Cake\TestSuite\TestCase; class UserTest extends TestCase { public function testPasswordIsHashed(): void { $user = new User(['password' => 'secret123']); $this->assertNotEquals('secret123', $user->password); $this->assertTrue((new \Authentication\PasswordHasher\DefaultPasswordHasher())->check('secret123', $user->password)); } public function testVirtualFieldFullName(): void { $user = new User(['first_name' => 'John', 'last_name' => 'Doe']); $this->assertSame('John Doe', $user->full_name); } } ``` --- ## 3. Controller Classes (`src/Controller/*Controller.php`) - **Base Class**: `Cake\TestSuite\TestCase` với `Cake\TestSuite\IntegrationTestTrait` - **Các điểm cần test**: - HTTP Status: `200` OK, `302` Redirect, `403` Forbidden, `404` Not Found, `422` Unprocessable Entity. - Flash messages: `$this->assertFlashMessage()`. - Session & Auth: Giả lập đăng nhập qua `$this->session()`. - CSRF & Security Tokens: Bắt buộc dùng `$this->enableCsrfToken()` và `$this->enableSecurityToken()` khi test POST/PUT/DELETE. ```php namespace App\Test\TestCase\Controller; use Cake\TestSuite\IntegrationTestTrait; use Cake\TestSuite\TestCase; class ArticlesControllerTest extends TestCase { use IntegrationTestTrait; protected array $fixtures = ['app.Articles', 'app.Users']; public function testAddWithoutAuthRedirectsToLogin(): void { $this->get('/articles/add'); $this->assertRedirectContains('/login'); } public function testAddSuccess(): void { $this->session(['Auth.User.id' => 1]); $this->enableCsrfToken(); $this->enableSecurityToken(); $this->post('/articles/add', [ 'title' => 'Sample Article', 'body' => 'Body text', 'published' => true, ]); $this->assertRedirect(['action' => 'index']); $this->assertFlashMessage('The article has been saved.'); } } ``` --- ## 4. Service Classes (`src/Service/*.php`) - **Base Class**: `Cake\TestSuite\TestCase` - **Các điểm cần test**: - Các dependency được mock bằng PHPUnit Mock hoặc Mockery. - Kiểm thử toàn bộ các nhánh rẽ điều kiện (Success, Invalid Data, 3rd-Party Failure). ```php namespace App\Test\TestCase\Service; use App\Service\PaymentProcessingService; use Cake\TestSuite\TestCase; class PaymentProcessingServiceTest extends TestCase { public function testChargeSuccess(): void { $clientMock = $this->createMock(\App\External\StripeClient::class); $clientMock->expects($this->once()) ->method('createCharge') ->willReturn(['id' => 'ch_123', 'status' => 'succeeded']); $service = new PaymentProcessingService($clientMock); $result = $service->processOrder(100.0, 'tok_visa'); $this->assertTrue($result->isSuccess()); } } ``` --- ## 5. Console Command Classes (`src/Command/*Command.php`) - **Base Class**: `Cake\TestSuite\TestCase` với `Cake\Console\TestSuite\ConsoleIntegrationTestTrait` - **Các điểm cần test**: - `assertExitSuccess()` và `assertExitError()`. - `assertOutputContains()`. - Arguments và Options flags. ```php namespace App\Test\TestCase\Command; use Cake\Console\TestSuite\ConsoleIntegrationTestTrait; use Cake\TestSuite\TestCase; class SyncInventoryCommandTest extends TestCase { use ConsoleIntegrationTestTrait; protected function setUp(): void { parent::setUp(); $this->useCommandRunner(); } public function testSyncAll(): void { $this->exec('sync_inventory --all'); $this->assertExitSuccess(); $this->assertOutputContains('Inventory sync completed.'); } } ``` --- ## 6. Controller Component Classes (`src/Controller/Component/*.php`) - Test logic tái sử dụng gắn vào Controller, dùng `ComponentRegistry`. --- ## 7. Middleware Classes (`src/Middleware/*.php`) - Test PSR-15 Middleware với `Cake\Http\ServerRequest` và Response Handler. --- ## 8. Mailer Classes (`src/Mailer/*Mailer.php`) - Sử dụng `Cake\TestSuite\EmailTrait`, kiểm tra email recipient, subject, và template body. --- ## 9. Form Classes (`src/Form/*Form.php`) - Test standalone forms không gắn với ORM table (ContactForm, SearchFilterForm). - Kiểm tra `validationDefault()` và `_execute()`. --- ## 10. Behavior Classes (`src/Model/Behavior/*.php`) - Test ORM Behavior hooks (`beforeSave`, `beforeFind`, `afterSave`).