|
| 1 | +{!! Blade::render(file_get_contents(base_path('vendor/laravel/boost/.ai/phpunit/core.blade.php')), ['assist' => $assist]) !!} |
| 2 | + |
| 3 | +### Suite Selection & Naming |
| 4 | + |
| 5 | +#### 1. Unit Tests (`*UnitTest.php`) |
| 6 | +- **Purpose**: Test a single class in isolation without Laravel's container or database. |
| 7 | +- **Base Class**: `Tests\Support\TestCases\UnitTestCase`. |
| 8 | +- **Location**: Mirror `app/` in `tests/App/` (e.g., `tests/App/Modules/Auth/DTOs/UserDataUnitTest.php`). |
| 9 | +- **Targets**: DTOs, Value Objects, Exceptions, pure logic classes. |
| 10 | +- **Preference**: Use whenever logic doesn't require database or complex service integration. |
| 11 | + |
| 12 | +#### 2. Functional Tests (`*FunctionalTest.php`) |
| 13 | +- **Purpose**: Test the integration of internal module components and business logic. |
| 14 | +- **Base Class**: `Tests\Support\TestCases\FunctionalTestCase`. |
| 15 | +- **Location**: Mirror `app/` in `tests/App/` (e.g., `tests/App/Modules/Auth/Actions/RegisterUserActionFunctionalTest.php`). |
| 16 | +- **Targets**: Actions (main entry points), Models (relationships/scopes), Service Providers. |
| 17 | +- **Entails**: Database access, Event fakes, mocking other module Actions. |
| 18 | + |
| 19 | +#### 3. Integration Tests (`*IntegrationTest.php`) |
| 20 | +- **Purpose**: Test the HTTP Transport layer and the full request/response cycle. |
| 21 | +- **Base Class**: `Tests\Support\TestCases\FunctionalTestCase`. |
| 22 | +- **Location**: Mirror `app/Http/` in `tests/Integration/Http/` (e.g., `tests/Integration/Http/Api/Auth/LoginIntegrationTest.php`). |
| 23 | +- **Targets**: Controllers, Form Requests, API Resources, Routing. |
| 24 | +- **Entails**: Mocking the business logic (Action) to focus strictly on the transport layer (validation, status codes, JSON structure). |
| 25 | +- **Mandatory**: Every Integration test MUST include a `test_it_integrates()` method that exercises the endpoint **without any mocking** to ensure the entire stack is correctly wired. This test does NOT need to be comprehensive; it should simply make a valid request and ensure it succeeds. |
| 26 | + |
| 27 | +#### Mirroring Rule |
| 28 | +- All tests in `tests/App/` and `tests/Integration/` MUST mirror the corresponding directory structure of the file being tested. |
| 29 | + |
| 30 | +### AAA+A Formatting |
| 31 | +*(Procedure: Refer to Skill: format-phpunit-tests)* |
| 32 | +- **Supported Blocks**: `// Arrange`, `// Anticipate` (for mocks/expectations), `// Act`, `// Assert`. |
| 33 | +- **Absolute Enclosure**: **NOTHING** must be defined outside of these blocks. Every piece of code MUST fall under one of these headers. |
| 34 | +- **Leave exactly ONE extra line break** after each comment (`// Arrange\n\n`). |
| 35 | +- **Never add extra content to the block comments**: Put the comment in a separate line, with a two line break apart from the block comment. |
| 36 | +<code-snippet name="Example clean AAA blocks" lang="php"> |
| 37 | + ❌ BAD |
| 38 | + // Arrange - Create a user in the US region so it triggers a {particular} pre-condition |
| 39 | + |
| 40 | + $user = User::region('us')->create(); |
| 41 | + |
| 42 | + --- |
| 43 | + |
| 44 | + ✅ GOOD |
| 45 | + // Arrange |
| 46 | + |
| 47 | + // Create a user in the US region so it triggers a {particular} pre-condition |
| 48 | + $user = User::region('us')->create(); |
| 49 | +</code-snippet> |
| 50 | + |
| 51 | +### Dependency Injection |
| 52 | +- **Always use `resolve(ClassName::class)`** - Laravel IoC will inject spies/mocks automatically. |
| 53 | +- **Create spies before calling `resolve()`.** |
| 54 | +- **Never manually instantiate with `new ClassName()`** if it has dependencies. Unless it is a Unit test. |
| 55 | + |
| 56 | +### Data Providers |
| 57 | +- **Always use `Generator`**. |
| 58 | +- **Use descriptive string keys**. |
| 59 | + |
| 60 | +<code-snippet name="Data Provider Example" lang="php"> |
| 61 | + public static function dataProvider(): Generator |
| 62 | + { |
| 63 | + yield 'successful scenario' => [ |
| 64 | + 'input' => 'valid', |
| 65 | + 'expected' => true, |
| 66 | + ]; |
| 67 | + |
| 68 | + yield 'unsuccessful scenario' => [ |
| 69 | + 'input' => 'invalid', |
| 70 | + 'expected' => false, |
| 71 | + ]; |
| 72 | + } |
| 73 | +</code-snippet> |
| 74 | + |
| 75 | +### Implementation Standards |
| 76 | +- **Use `spy()` over `mock()`** unless you need to set expectations (Anticipate block). |
| 77 | +- **Always use `LogFake::bind()`** to mock logs. |
| 78 | +- **Never test private methods directly**. |
| 79 | +- **NEVER test controllers directly** (use HTTP endpoint integration tests). |
| 80 | +- **Always add `CoversClass(ClassName::class)` attirubte(s)** to all tests adding the classes being covered. |
0 commit comments