@famgia/omnify-laravel 0.0.87 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
  2. package/dist/chunk-V7LWJ6OM.js.map +1 -0
  3. package/dist/index.cjs +180 -11
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +48 -1
  6. package/dist/index.d.ts +48 -1
  7. package/dist/index.js +5 -1
  8. package/dist/plugin.cjs +176 -11
  9. package/dist/plugin.cjs.map +1 -1
  10. package/dist/plugin.js +1 -1
  11. package/package.json +5 -5
  12. package/scripts/postinstall.js +29 -36
  13. package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
  14. package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
  15. package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
  16. package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
  17. package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
  18. package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
  19. package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
  20. package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
  21. package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
  22. package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
  23. package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
  24. package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
  25. package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
  26. package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
  27. package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
  28. package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
  29. package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
  30. package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
  31. package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
  32. package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
  33. package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
  34. package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
  35. package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
  36. package/stubs/ai-guides/laravel/README.md.stub +59 -0
  37. package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
  38. package/stubs/ai-guides/laravel/controller.md.stub +484 -0
  39. package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
  40. package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
  41. package/stubs/ai-guides/laravel/request.md.stub +450 -0
  42. package/stubs/ai-guides/laravel/resource.md.stub +516 -0
  43. package/stubs/ai-guides/laravel/service.md.stub +503 -0
  44. package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
  45. package/ai-guides/laravel-guide.md +0 -461
  46. package/dist/chunk-YVVAJA3T.js.map +0 -1
@@ -0,0 +1,1504 @@
1
+ # Testing Guide (PEST)
2
+
3
+ > **Related:** [README](./README.md) | [Naming Conventions](./naming-conventions.md) | [Checklist](./checklist.md)
4
+
5
+ ## Quick Start - How to Run Tests
6
+
7
+ ```bash
8
+ # Run ALL tests (from project root - uses Docker wrapper)
9
+ ./artisan test
10
+
11
+ # Run specific test file
12
+ ./artisan test --filter=UserControllerTest
13
+
14
+ # Run specific test method
15
+ ./artisan test --filter="creates user with valid data"
16
+
17
+ # Run with verbose output
18
+ ./artisan test -v
19
+ ```
20
+
21
+ > **Note:** The root `./artisan` script is a wrapper that runs `docker compose exec backend php artisan` automatically.
22
+
23
+ ## Critical: Database Trait
24
+
25
+ **MUST use `RefreshDatabase` for SQLite in-memory testing:**
26
+
27
+ ```php
28
+ // ✅ CORRECT - Use RefreshDatabase
29
+ use Illuminate\Foundation\Testing\RefreshDatabase;
30
+ uses(RefreshDatabase::class);
31
+
32
+ // ❌ WRONG - DatabaseTransactions doesn't run migrations
33
+ // Will fail with "no such table" error!
34
+ use Illuminate\Foundation\Testing\DatabaseTransactions;
35
+ uses(DatabaseTransactions::class);
36
+ ```
37
+
38
+ | Trait | Database | Behavior |
39
+ | ---------------------- | ---------------- | --------------------------------------------- |
40
+ | `RefreshDatabase` | SQLite in-memory | Runs migrations → truncates after each test |
41
+ | `DatabaseTransactions` | MySQL/PostgreSQL | Only wraps in transaction (tables must exist) |
42
+
43
+ ---
44
+
45
+ ## Overview
46
+
47
+ This project uses **PEST** for testing. Every API endpoint MUST have tests covering:
48
+
49
+ - **Normal cases (正常系)** - Happy path, expected behavior
50
+ - **Abnormal cases (異常系)** - Validation errors, edge cases
51
+
52
+ **Principle**: If you can't test it, you can't ship it.
53
+
54
+ ---
55
+
56
+ ## Test Categories
57
+
58
+ | Category | Description | HTTP Codes |
59
+ | --------------------- | --------------------------------- | ------------- |
60
+ | **Normal (正常系)** | Happy path, expected behavior | 200, 201, 204 |
61
+ | **Abnormal (異常系)** | Validation errors, business rules | 422 |
62
+ | **Not Found** | Resource doesn't exist | 404 |
63
+ | **Auth Required** | Unauthenticated request | 401 |
64
+ | **Forbidden** | Unauthorized action | 403 |
65
+
66
+ ---
67
+
68
+ ## Test Structure
69
+
70
+ ```
71
+ tests/
72
+ ├── Feature/
73
+ │ ├── Api/ # API endpoint tests
74
+ │ │ ├── UserControllerTest.php
75
+ │ │ └── PostControllerTest.php
76
+ │ └── Auth/ # Authentication flow tests
77
+ │ ├── LoginTest.php
78
+ │ └── RegistrationTest.php
79
+ ├── Unit/
80
+ │ ├── Services/ # Service class tests
81
+ │ │ └── OrderServiceTest.php
82
+ │ ├── Models/ # Model tests (accessors, scopes)
83
+ │ │ └── UserTest.php
84
+ │ └── Rules/ # Custom validation rules
85
+ │ └── KatakanaRuleTest.php
86
+ └── Pest.php # PEST configuration
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Feature vs Unit Tests
92
+
93
+ ### When to Use Feature Tests
94
+
95
+ **Feature tests** test the full HTTP request/response cycle.
96
+
97
+ ```php
98
+ // ✅ Feature Test: Full HTTP cycle
99
+ // tests/Feature/Api/UserControllerTest.php
100
+ it('正常: creates user with valid data', function () {
101
+ $response = $this->postJson('/api/users', validUserData());
102
+
103
+ $response->assertCreated();
104
+ $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
105
+ });
106
+ ```
107
+
108
+ **Use Feature tests for:**
109
+ - API endpoints (Controllers)
110
+ - Authentication flows (Login, Logout, Register)
111
+ - Middleware behavior
112
+ - Full request validation
113
+ - Database state verification
114
+
115
+ ### When to Use Unit Tests
116
+
117
+ **Unit tests** test isolated classes/methods without HTTP.
118
+
119
+ ```php
120
+ // ✅ Unit Test: Isolated logic
121
+ // tests/Unit/Services/OrderServiceTest.php
122
+ it('正常: calculates total with tax', function () {
123
+ $service = new OrderService();
124
+
125
+ $total = $service->calculateTotal(items: $items, taxRate: 0.1);
126
+
127
+ expect($total)->toBe(1100);
128
+ });
129
+
130
+ // tests/Unit/Models/UserTest.php
131
+ it('正常: returns full name', function () {
132
+ $user = new User([
133
+ 'name_lastname' => '田中',
134
+ 'name_firstname' => '太郎',
135
+ ]);
136
+
137
+ expect($user->name_full_name)->toBe('田中 太郎');
138
+ });
139
+ ```
140
+
141
+ **Use Unit tests for:**
142
+ - Service/Action classes
143
+ - Model accessors/mutators
144
+ - Model scopes
145
+ - Helper functions
146
+ - Custom validation rules
147
+ - Pure business logic
148
+
149
+ ### Decision Matrix
150
+
151
+ | What to Test | Test Type | Location |
152
+ | --------------- | --------- | --------------------- |
153
+ | API endpoint | Feature | `Feature/Api/` |
154
+ | Login/Logout | Feature | `Feature/Auth/` |
155
+ | Middleware | Feature | `Feature/Middleware/` |
156
+ | Service class | Unit | `Unit/Services/` |
157
+ | Model accessor | Unit | `Unit/Models/` |
158
+ | Custom rule | Unit | `Unit/Rules/` |
159
+ | Helper function | Unit | `Unit/Helpers/` |
160
+
161
+ ---
162
+
163
+ ## Mocking & Faking
164
+
165
+ ### Fake Mail
166
+
167
+ ```php
168
+ use Illuminate\Support\Facades\Mail;
169
+ use App\Mail\WelcomeEmail;
170
+
171
+ it('正常: sends welcome email on registration', function () {
172
+ Mail::fake();
173
+
174
+ $this->postJson('/api/register', validUserData())
175
+ ->assertCreated();
176
+
177
+ Mail::assertSent(WelcomeEmail::class, function ($mail) {
178
+ return $mail->hasTo('test@example.com');
179
+ });
180
+ });
181
+
182
+ it('異常: does not send email on validation failure', function () {
183
+ Mail::fake();
184
+
185
+ $this->postJson('/api/register', [])
186
+ ->assertUnprocessable();
187
+
188
+ Mail::assertNothingSent();
189
+ });
190
+ ```
191
+
192
+ ### Fake Queue
193
+
194
+ ```php
195
+ use Illuminate\Support\Facades\Queue;
196
+ use App\Jobs\ProcessOrder;
197
+
198
+ it('正常: dispatches job on order creation', function () {
199
+ Queue::fake();
200
+
201
+ $this->postJson('/api/orders', validOrderData())
202
+ ->assertCreated();
203
+
204
+ Queue::assertPushed(ProcessOrder::class);
205
+ });
206
+ ```
207
+
208
+ ### Fake Storage
209
+
210
+ ```php
211
+ use Illuminate\Support\Facades\Storage;
212
+ use Illuminate\Http\UploadedFile;
213
+
214
+ it('正常: uploads avatar', function () {
215
+ Storage::fake('public');
216
+
217
+ $file = UploadedFile::fake()->image('avatar.jpg');
218
+
219
+ $this->postJson('/api/users/avatar', ['avatar' => $file])
220
+ ->assertOk();
221
+
222
+ Storage::disk('public')->assertExists('avatars/' . $file->hashName());
223
+ });
224
+ ```
225
+
226
+ ### Fake Notification
227
+
228
+ ```php
229
+ use Illuminate\Support\Facades\Notification;
230
+ use App\Notifications\OrderShipped;
231
+
232
+ it('正常: notifies user when order ships', function () {
233
+ Notification::fake();
234
+
235
+ $order = Order::factory()->create();
236
+
237
+ $this->postJson("/api/orders/{$order->id}/ship")
238
+ ->assertOk();
239
+
240
+ Notification::assertSentTo($order->user, OrderShipped::class);
241
+ });
242
+ ```
243
+
244
+ ### Fake HTTP (External APIs)
245
+
246
+ ```php
247
+ use Illuminate\Support\Facades\Http;
248
+
249
+ it('正常: fetches data from external API', function () {
250
+ Http::fake([
251
+ 'api.example.com/*' => Http::response(['data' => 'value'], 200),
252
+ ]);
253
+
254
+ $response = $this->getJson('/api/external-data');
255
+
256
+ $response->assertOk()
257
+ ->assertJsonPath('data', 'value');
258
+ });
259
+
260
+ it('異常: handles external API failure', function () {
261
+ Http::fake([
262
+ 'api.example.com/*' => Http::response([], 500),
263
+ ]);
264
+
265
+ $response = $this->getJson('/api/external-data');
266
+
267
+ $response->assertServiceUnavailable();
268
+ });
269
+ ```
270
+
271
+ ### Mock Service
272
+
273
+ ```php
274
+ use App\Services\PaymentService;
275
+
276
+ it('正常: processes payment', function () {
277
+ $mock = Mockery::mock(PaymentService::class);
278
+ $mock->shouldReceive('charge')
279
+ ->once()
280
+ ->with(1000, 'tok_visa')
281
+ ->andReturn(true);
282
+
283
+ $this->app->instance(PaymentService::class, $mock);
284
+
285
+ $this->postJson('/api/orders/pay', ['token' => 'tok_visa'])
286
+ ->assertOk();
287
+ });
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Authentication Tests
293
+
294
+ ### Login Tests
295
+
296
+ ```php
297
+ // tests/Feature/Auth/LoginTest.php
298
+
299
+ describe('POST /api/login', function () {
300
+
301
+ it('正常: logs in with valid credentials', function () {
302
+ $user = User::factory()->create([
303
+ 'email' => 'test@example.com',
304
+ 'password' => 'password123',
305
+ ]);
306
+
307
+ $response = $this->postJson('/api/login', [
308
+ 'email' => 'test@example.com',
309
+ 'password' => 'password123',
310
+ ]);
311
+
312
+ $response->assertOk()
313
+ ->assertJsonStructure(['token', 'user']);
314
+ });
315
+
316
+ it('異常: fails with wrong password', function () {
317
+ User::factory()->create([
318
+ 'email' => 'test@example.com',
319
+ 'password' => 'password123',
320
+ ]);
321
+
322
+ $response = $this->postJson('/api/login', [
323
+ 'email' => 'test@example.com',
324
+ 'password' => 'wrongpassword',
325
+ ]);
326
+
327
+ $response->assertUnauthorized();
328
+ });
329
+
330
+ it('異常: fails with nonexistent email', function () {
331
+ $response = $this->postJson('/api/login', [
332
+ 'email' => 'notexist@example.com',
333
+ 'password' => 'password123',
334
+ ]);
335
+
336
+ $response->assertUnauthorized();
337
+ });
338
+ });
339
+ ```
340
+
341
+ ### Logout Tests
342
+
343
+ ```php
344
+ describe('POST /api/logout', function () {
345
+
346
+ it('正常: logs out authenticated user', function () {
347
+ $user = User::factory()->create();
348
+
349
+ $response = $this->actingAs($user)
350
+ ->postJson('/api/logout');
351
+
352
+ $response->assertNoContent();
353
+ });
354
+
355
+ it('異常: returns 401 when not authenticated', function () {
356
+ $response = $this->postJson('/api/logout');
357
+
358
+ $response->assertUnauthorized();
359
+ });
360
+ });
361
+ ```
362
+
363
+ ### Protected Route Tests
364
+
365
+ ```php
366
+ describe('Protected routes', function () {
367
+
368
+ it('正常: allows authenticated user', function () {
369
+ $user = User::factory()->create();
370
+
371
+ $response = $this->actingAs($user)
372
+ ->getJson('/api/me');
373
+
374
+ $response->assertOk()
375
+ ->assertJsonPath('data.id', $user->id);
376
+ });
377
+
378
+ it('異常: returns 401 without token', function () {
379
+ $response = $this->getJson('/api/me');
380
+
381
+ $response->assertUnauthorized();
382
+ });
383
+
384
+ it('異常: returns 401 with invalid token', function () {
385
+ $response = $this->getJson('/api/me', [
386
+ 'Authorization' => 'Bearer invalid_token',
387
+ ]);
388
+
389
+ $response->assertUnauthorized();
390
+ });
391
+ });
392
+ ```
393
+
394
+ ### Token Refresh Tests (if applicable)
395
+
396
+ ```php
397
+ describe('POST /api/refresh', function () {
398
+
399
+ it('正常: refreshes token', function () {
400
+ $user = User::factory()->create();
401
+ $token = $user->createToken('test')->plainTextToken;
402
+
403
+ $response = $this->withHeader('Authorization', "Bearer {$token}")
404
+ ->postJson('/api/refresh');
405
+
406
+ $response->assertOk()
407
+ ->assertJsonStructure(['token']);
408
+ });
409
+ });
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Middleware Tests
415
+
416
+ ### Rate Limiting
417
+
418
+ ```php
419
+ describe('Rate limiting', function () {
420
+
421
+ it('異常: returns 429 when rate limit exceeded', function () {
422
+ $user = User::factory()->create();
423
+
424
+ // Hit the endpoint many times
425
+ for ($i = 0; $i < 60; $i++) {
426
+ $this->actingAs($user)->getJson('/api/users');
427
+ }
428
+
429
+ // Next request should be rate limited
430
+ $response = $this->actingAs($user)->getJson('/api/users');
431
+
432
+ $response->assertStatus(429);
433
+ });
434
+ });
435
+ ```
436
+
437
+ ### CORS (if custom implementation)
438
+
439
+ ```php
440
+ describe('CORS', function () {
441
+
442
+ it('正常: includes CORS headers', function () {
443
+ $response = $this->getJson('/api/users');
444
+
445
+ $response->assertHeader('Access-Control-Allow-Origin');
446
+ });
447
+
448
+ it('正常: handles preflight OPTIONS request', function () {
449
+ $response = $this->options('/api/users', [], [
450
+ 'Origin' => 'http://localhost:3000',
451
+ 'Access-Control-Request-Method' => 'POST',
452
+ ]);
453
+
454
+ $response->assertOk()
455
+ ->assertHeader('Access-Control-Allow-Methods');
456
+ });
457
+ });
458
+ ```
459
+
460
+ ### Custom Middleware
461
+
462
+ ```php
463
+ // Example: Admin only middleware
464
+ describe('Admin middleware', function () {
465
+
466
+ it('正常: allows admin user', function () {
467
+ $admin = User::factory()->create(['role' => 'admin']);
468
+
469
+ $response = $this->actingAs($admin)
470
+ ->getJson('/api/admin/dashboard');
471
+
472
+ $response->assertOk();
473
+ });
474
+
475
+ it('異常: returns 403 for non-admin', function () {
476
+ $user = User::factory()->create(['role' => 'user']);
477
+
478
+ $response = $this->actingAs($user)
479
+ ->getJson('/api/admin/dashboard');
480
+
481
+ $response->assertForbidden();
482
+ });
483
+ });
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Naming Conventions
489
+
490
+ > **See:** [Naming Conventions - Test Naming](./naming-conventions.md#test-naming-pest) for complete naming rules
491
+
492
+ ### Quick Reference
493
+
494
+ | Category | Prefix | Example |
495
+ | --------------------- | ------- | ----------------------------------------------------- |
496
+ | **正常系 (Normal)** | `正常:` | `it('正常: creates user with valid data')` |
497
+ | **異常系 (Abnormal)** | `異常:` | `it('異常: fails to create user with invalid email')` |
498
+
499
+ ### Test File Names
500
+
501
+ ```
502
+ {Model}ControllerTest.php # Feature tests for API
503
+ {Model}Test.php # Unit tests for Model
504
+ {Service}Test.php # Unit tests for Service
505
+ ```
506
+
507
+ ---
508
+
509
+ ## Test Coverage Matrix
510
+
511
+ ### CRUD Endpoint Coverage
512
+
513
+ | Endpoint | Normal (正常系) | Abnormal (異常系) |
514
+ | ----------------------------------------- | ----------------------- | ----------------------- |
515
+ | **GET /api/{resource}** (index) | Returns paginated list | Empty list when no data |
516
+ | | Filters by search | Invalid query params |
517
+ | | Sorts by field | Invalid sort field |
518
+ | | Pagination works | |
519
+ | **POST /api/{resource}** (store) | Creates with valid data | Missing required fields |
520
+ | | Returns 201 + resource | Invalid field format |
521
+ | | | Duplicate unique field |
522
+ | | | Validation errors (422) |
523
+ | **GET /api/{resource}/{id}** (show) | Returns resource | Not found (404) |
524
+ | | | Invalid ID format |
525
+ | **PUT /api/{resource}/{id}** (update) | Updates with valid data | Not found (404) |
526
+ | | Partial update works | Invalid data (422) |
527
+ | | | Duplicate unique field |
528
+ | **DELETE /api/{resource}/{id}** (destroy) | Deletes resource | Not found (404) |
529
+ | | Returns 204 | |
530
+
531
+ ### Field Validation Coverage
532
+
533
+ | Field Type | Normal (正常系) | Abnormal (異常系) |
534
+ | ------------ | ---------------- | ------------------------ |
535
+ | **required** | Field present | Field missing |
536
+ | | | Field empty |
537
+ | | | Field null |
538
+ | **string** | Valid string | Non-string type |
539
+ | | | Too long (max) |
540
+ | **email** | Valid email | Invalid format |
541
+ | | | Missing @ |
542
+ | **unique** | New value | Duplicate value |
543
+ | **min:N** | At limit | Below limit |
544
+ | **max:N** | At limit | Above limit |
545
+ | **integer** | Valid integer | Non-integer |
546
+ | | | Negative (if applicable) |
547
+ | **date** | Valid date | Invalid format |
548
+ | **enum** | Valid option | Invalid option |
549
+ | **regex** | Matching pattern | Non-matching pattern |
550
+
551
+ ### Authentication & Authorization Coverage
552
+
553
+ | Scenario | Normal (正常系) | Abnormal (異常系) |
554
+ | -------------------- | ----------------------- | --------------------- |
555
+ | **No auth required** | Returns data | - |
556
+ | **Auth required** | Authenticated → success | Unauthenticated → 401 |
557
+ | **Owner only** | Owner → success | Non-owner → 403 |
558
+ | **Admin only** | Admin → success | Non-admin → 403 |
559
+ | **Role-based** | Has role → success | Missing role → 403 |
560
+
561
+ ### Japanese Field Validation Coverage
562
+
563
+ | Field | Normal (正常系) | Abnormal (異常系) |
564
+ | --------------------- | --------------------- | ------------------------------ |
565
+ | `name_lastname` | Valid kanji/hiragana | Empty, too long (>50) |
566
+ | `name_firstname` | Valid kanji/hiragana | Empty, too long (>50) |
567
+ | `name_kana_lastname` | Valid katakana | Hiragana, kanji, romaji |
568
+ | `name_kana_firstname` | Valid katakana | Hiragana, kanji, romaji |
569
+ | `phone` | Valid Japanese format | Invalid format, too short/long |
570
+ | `postal_code` | 7 digits (no hyphen) | With hyphen, wrong length |
571
+
572
+ ---
573
+
574
+ ## PEST Test Template (CRUD)
575
+
576
+ ```php
577
+ <?php
578
+
579
+ use App\Models\User;
580
+
581
+ // Helper function for valid user data
582
+ function validUserData(array $overrides = []): array
583
+ {
584
+ return array_merge([
585
+ 'name_lastname' => 'Tanaka',
586
+ 'name_firstname' => 'Taro',
587
+ 'name_kana_lastname' => 'タナカ',
588
+ 'name_kana_firstname' => 'タロウ',
589
+ 'email' => 'tanaka@example.com',
590
+ 'password' => 'password123',
591
+ ], $overrides);
592
+ }
593
+
594
+ // =============================================================================
595
+ // INDEX (GET /api/users)
596
+ // =============================================================================
597
+
598
+ describe('GET /api/users', function () {
599
+
600
+ // Normal cases (正常系)
601
+
602
+ it('正常: returns paginated users', function () {
603
+ User::factory()->count(15)->create();
604
+
605
+ $response = $this->getJson('/api/users');
606
+
607
+ $response->assertOk()
608
+ ->assertJsonStructure([
609
+ 'data' => [['id', 'name_lastname', 'name_firstname', 'email']],
610
+ 'meta' => ['current_page', 'last_page', 'per_page', 'total'],
611
+ ])
612
+ ->assertJsonCount(10, 'data');
613
+ });
614
+
615
+ it('正常: filters users by search term', function () {
616
+ User::factory()->create(['name_lastname' => 'Tanaka']);
617
+ User::factory()->create(['name_lastname' => 'Yamada']);
618
+
619
+ $response = $this->getJson('/api/users?search=Tanaka');
620
+
621
+ $response->assertOk()
622
+ ->assertJsonCount(1, 'data');
623
+ });
624
+
625
+ it('正常: sorts users by specified field', function () {
626
+ User::factory()->create(['name_lastname' => 'B']);
627
+ User::factory()->create(['name_lastname' => 'A']);
628
+
629
+ $response = $this->getJson('/api/users?sort_by=name_lastname&sort_order=asc');
630
+
631
+ $response->assertOk();
632
+ expect($response->json('data.0.name_lastname'))->toBe('A');
633
+ });
634
+
635
+ it('正常: paginates with custom per_page', function () {
636
+ User::factory()->count(10)->create();
637
+
638
+ $response = $this->getJson('/api/users?per_page=5');
639
+
640
+ $response->assertOk()
641
+ ->assertJsonCount(5, 'data')
642
+ ->assertJsonPath('meta.per_page', 5);
643
+ });
644
+
645
+ // Abnormal cases (異常系)
646
+
647
+ it('異常: returns empty array when no users exist', function () {
648
+ $response = $this->getJson('/api/users');
649
+
650
+ $response->assertOk()
651
+ ->assertJsonCount(0, 'data');
652
+ });
653
+ });
654
+
655
+ // =============================================================================
656
+ // STORE (POST /api/users)
657
+ // =============================================================================
658
+
659
+ describe('POST /api/users', function () {
660
+
661
+ // Normal cases (正常系)
662
+
663
+ it('正常: creates user with valid data', function () {
664
+ $data = validUserData();
665
+
666
+ $response = $this->postJson('/api/users', $data);
667
+
668
+ $response->assertCreated()
669
+ ->assertJsonPath('data.email', 'tanaka@example.com');
670
+
671
+ $this->assertDatabaseHas('users', ['email' => 'tanaka@example.com']);
672
+ });
673
+
674
+ // Abnormal cases (異常系)
675
+
676
+ it('異常: fails with missing required fields', function () {
677
+ $response = $this->postJson('/api/users', []);
678
+
679
+ $response->assertUnprocessable()
680
+ ->assertJsonValidationErrors([
681
+ 'name_lastname',
682
+ 'name_firstname',
683
+ 'name_kana_lastname',
684
+ 'name_kana_firstname',
685
+ 'email',
686
+ 'password',
687
+ ]);
688
+ });
689
+
690
+ it('異常: fails with invalid email format', function () {
691
+ $response = $this->postJson('/api/users', validUserData([
692
+ 'email' => 'invalid-email',
693
+ ]));
694
+
695
+ $response->assertUnprocessable()
696
+ ->assertJsonValidationErrors(['email']);
697
+ });
698
+
699
+ it('異常: fails with duplicate email', function () {
700
+ User::factory()->create(['email' => 'existing@example.com']);
701
+
702
+ $response = $this->postJson('/api/users', validUserData([
703
+ 'email' => 'existing@example.com',
704
+ ]));
705
+
706
+ $response->assertUnprocessable()
707
+ ->assertJsonValidationErrors(['email']);
708
+ });
709
+
710
+ it('異常: fails with password too short', function () {
711
+ $response = $this->postJson('/api/users', validUserData([
712
+ 'password' => '123',
713
+ ]));
714
+
715
+ $response->assertUnprocessable()
716
+ ->assertJsonValidationErrors(['password']);
717
+ });
718
+ });
719
+
720
+ // =============================================================================
721
+ // SHOW (GET /api/users/{id})
722
+ // =============================================================================
723
+
724
+ describe('GET /api/users/{id}', function () {
725
+
726
+ // Normal cases (正常系)
727
+
728
+ it('正常: returns user by id', function () {
729
+ $user = User::factory()->create();
730
+
731
+ $response = $this->getJson("/api/users/{$user->id}");
732
+
733
+ $response->assertOk()
734
+ ->assertJsonPath('data.id', $user->id)
735
+ ->assertJsonPath('data.email', $user->email);
736
+ });
737
+
738
+ // Abnormal cases (異常系)
739
+
740
+ it('異常: returns 404 for nonexistent user', function () {
741
+ $response = $this->getJson('/api/users/99999');
742
+
743
+ $response->assertNotFound();
744
+ });
745
+ });
746
+
747
+ // =============================================================================
748
+ // UPDATE (PUT /api/users/{id})
749
+ // =============================================================================
750
+
751
+ describe('PUT /api/users/{id}', function () {
752
+
753
+ // Normal cases (正常系)
754
+
755
+ it('正常: updates user with valid data', function () {
756
+ $user = User::factory()->create();
757
+
758
+ $response = $this->putJson("/api/users/{$user->id}", [
759
+ 'name_lastname' => 'Yamada',
760
+ ]);
761
+
762
+ $response->assertOk()
763
+ ->assertJsonPath('data.name_lastname', 'Yamada');
764
+
765
+ $this->assertDatabaseHas('users', [
766
+ 'id' => $user->id,
767
+ 'name_lastname' => 'Yamada',
768
+ ]);
769
+ });
770
+
771
+ it('正常: allows partial update', function () {
772
+ $user = User::factory()->create(['name_lastname' => 'Tanaka']);
773
+
774
+ $response = $this->putJson("/api/users/{$user->id}", [
775
+ 'name_firstname' => 'Jiro',
776
+ ]);
777
+
778
+ $response->assertOk();
779
+ $this->assertDatabaseHas('users', [
780
+ 'id' => $user->id,
781
+ 'name_lastname' => 'Tanaka', // Unchanged
782
+ 'name_firstname' => 'Jiro', // Updated
783
+ ]);
784
+ });
785
+
786
+ it('正常: allows keeping same email', function () {
787
+ $user = User::factory()->create(['email' => 'same@example.com']);
788
+
789
+ $response = $this->putJson("/api/users/{$user->id}", [
790
+ 'email' => 'same@example.com',
791
+ ]);
792
+
793
+ $response->assertOk();
794
+ });
795
+
796
+ // Abnormal cases (異常系)
797
+
798
+ it('異常: returns 404 for nonexistent user', function () {
799
+ $response = $this->putJson('/api/users/99999', [
800
+ 'name_lastname' => 'Yamada',
801
+ ]);
802
+
803
+ $response->assertNotFound();
804
+ });
805
+
806
+ it('異常: fails with duplicate email', function () {
807
+ $user1 = User::factory()->create(['email' => 'user1@example.com']);
808
+ User::factory()->create(['email' => 'user2@example.com']);
809
+
810
+ $response = $this->putJson("/api/users/{$user1->id}", [
811
+ 'email' => 'user2@example.com',
812
+ ]);
813
+
814
+ $response->assertUnprocessable()
815
+ ->assertJsonValidationErrors(['email']);
816
+ });
817
+ });
818
+
819
+ // =============================================================================
820
+ // DESTROY (DELETE /api/users/{id})
821
+ // =============================================================================
822
+
823
+ describe('DELETE /api/users/{id}', function () {
824
+
825
+ // Normal cases (正常系)
826
+
827
+ it('正常: deletes user', function () {
828
+ $user = User::factory()->create();
829
+
830
+ $response = $this->deleteJson("/api/users/{$user->id}");
831
+
832
+ $response->assertNoContent();
833
+ $this->assertDatabaseMissing('users', ['id' => $user->id]);
834
+ });
835
+
836
+ // Abnormal cases (異常系)
837
+
838
+ it('異常: returns 404 for nonexistent user', function () {
839
+ $response = $this->deleteJson('/api/users/99999');
840
+
841
+ $response->assertNotFound();
842
+ });
843
+ });
844
+
845
+ // =============================================================================
846
+ // AUTHENTICATION (401)
847
+ // =============================================================================
848
+
849
+ // Uncomment when auth is required
850
+ // describe('Authentication', function () {
851
+ // it('異常: returns 401 when unauthenticated for index', function () {
852
+ // $response = $this->getJson('/api/users');
853
+ // $response->assertUnauthorized();
854
+ // });
855
+ //
856
+ // it('異常: returns 401 when unauthenticated for store', function () {
857
+ // $response = $this->postJson('/api/users', validUserData());
858
+ // $response->assertUnauthorized();
859
+ // });
860
+ // });
861
+
862
+ // =============================================================================
863
+ // AUTHORIZATION (403)
864
+ // =============================================================================
865
+
866
+ // Uncomment when authorization is required
867
+ // describe('Authorization', function () {
868
+ // it('異常: returns 403 when updating other user', function () {
869
+ // $user = User::factory()->create();
870
+ // $otherUser = User::factory()->create();
871
+ //
872
+ // $this->actingAs($otherUser);
873
+ //
874
+ // $response = $this->putJson("/api/users/{$user->id}", [
875
+ // 'name_lastname' => 'Yamada',
876
+ // ]);
877
+ //
878
+ // $response->assertForbidden();
879
+ // });
880
+ //
881
+ // it('異常: returns 403 when deleting without admin role', function () {
882
+ // $user = User::factory()->create();
883
+ // $normalUser = User::factory()->create();
884
+ //
885
+ // $this->actingAs($normalUser);
886
+ //
887
+ // $response = $this->deleteJson("/api/users/{$user->id}");
888
+ //
889
+ // $response->assertForbidden();
890
+ // });
891
+ // });
892
+
893
+ // =============================================================================
894
+ // JAPANESE FIELD VALIDATION
895
+ // =============================================================================
896
+
897
+ describe('Japanese field validation', function () {
898
+
899
+ it('異常: fails with hiragana in kana field', function () {
900
+ $response = $this->postJson('/api/users', validUserData([
901
+ 'name_kana_lastname' => 'たなか', // Hiragana - should be Katakana
902
+ ]));
903
+
904
+ $response->assertUnprocessable()
905
+ ->assertJsonValidationErrors(['name_kana_lastname']);
906
+ });
907
+
908
+ it('異常: fails with romaji in kana field', function () {
909
+ $response = $this->postJson('/api/users', validUserData([
910
+ 'name_kana_lastname' => 'Tanaka', // Romaji - should be Katakana
911
+ ]));
912
+
913
+ $response->assertUnprocessable()
914
+ ->assertJsonValidationErrors(['name_kana_lastname']);
915
+ });
916
+
917
+ it('正常: accepts valid katakana', function () {
918
+ $response = $this->postJson('/api/users', validUserData([
919
+ 'name_kana_lastname' => 'タナカ',
920
+ 'name_kana_firstname' => 'タロウ',
921
+ ]));
922
+
923
+ $response->assertCreated();
924
+ });
925
+
926
+ it('正常: accepts katakana with long vowel mark', function () {
927
+ $response = $this->postJson('/api/users', validUserData([
928
+ 'name_kana_lastname' => 'サトー',
929
+ 'name_kana_firstname' => 'ユーコ',
930
+ ]));
931
+
932
+ $response->assertCreated();
933
+ });
934
+
935
+ it('異常: fails with name exceeding max length', function () {
936
+ $response = $this->postJson('/api/users', validUserData([
937
+ 'name_lastname' => str_repeat('a', 51), // Over 50 chars
938
+ ]));
939
+
940
+ $response->assertUnprocessable()
941
+ ->assertJsonValidationErrors(['name_lastname']);
942
+ });
943
+ });
944
+ ```
945
+
946
+ ---
947
+
948
+ ## Test Checklist
949
+
950
+ ### Per Endpoint
951
+
952
+ - [ ] **Normal cases (正常系)**
953
+ - [ ] Happy path with valid data
954
+ - [ ] All optional features work (search, sort, pagination)
955
+ - [ ] Response structure is correct
956
+ - [ ] Database state is correct
957
+
958
+ - [ ] **Abnormal cases (異常系)**
959
+ - [ ] 404 Not Found for missing resource
960
+ - [ ] 422 Validation Error for invalid data
961
+ - [ ] All required fields checked
962
+ - [ ] All format validations checked
963
+ - [ ] Unique constraints checked
964
+
965
+ ### Per Field (Validation)
966
+
967
+ For each field in FormRequest:
968
+
969
+ - [ ] **Present & valid** → Success
970
+ - [ ] **Missing (if required)** → 422
971
+ - [ ] **Empty string** → 422 (if required)
972
+ - [ ] **Invalid format** → 422
973
+ - [ ] **Exceeds max length** → 422
974
+ - [ ] **Below min length** → 422
975
+ - [ ] **Duplicate (if unique)** → 422
976
+
977
+ ---
978
+
979
+ ## Testing Database Setup
980
+
981
+ Tests use a separate database `omnify_testing` (auto-created by Docker).
982
+
983
+ **Files:**
984
+ - `docker/mysql/init/01-create-testing-db.sql` - Creates testing database on Docker init
985
+ - `backend/.env.testing` - Testing environment config (generated by `npm run setup`)
986
+
987
+ **Key differences from `.env`:**
988
+
989
+ | Setting | `.env` (dev) | `.env.testing` |
990
+ | ------------------ | ------------ | ---------------- |
991
+ | `DB_DATABASE` | `omnify` | `omnify_testing` |
992
+ | `SESSION_DRIVER` | `cookie` | `array` |
993
+ | `CACHE_STORE` | `file` | `array` |
994
+ | `QUEUE_CONNECTION` | `sync` | `sync` |
995
+ | `MAIL_MAILER` | `smtp` | `array` |
996
+ | `BCRYPT_ROUNDS` | `12` | `4` (faster) |
997
+
998
+ ---
999
+
1000
+ ## Running Tests
1001
+
1002
+ ```bash
1003
+ # Run all tests (from project root)
1004
+ ./artisan test
1005
+
1006
+ # Run specific test file
1007
+ ./artisan test --filter=UserControllerTest
1008
+
1009
+ # Run specific describe block
1010
+ ./artisan test --filter="GET /api/users"
1011
+
1012
+ # Run specific test
1013
+ ./artisan test --filter="creates user with valid data"
1014
+
1015
+ # Run with coverage
1016
+ ./artisan test --coverage
1017
+
1018
+ # Run in parallel
1019
+ ./artisan test --parallel
1020
+ ```
1021
+
1022
+ ### First Time Setup
1023
+
1024
+ If testing database doesn't exist:
1025
+
1026
+ ```bash
1027
+ # Option 1: Recreate Docker containers (recommended)
1028
+ docker compose down -v
1029
+ docker compose up -d
1030
+
1031
+ # Option 2: Create manually
1032
+ docker compose exec mysql mysql -uroot -proot -e "CREATE DATABASE omnify_testing; GRANT ALL ON omnify_testing.* TO 'omnify'@'%';"
1033
+ ```
1034
+
1035
+ ---
1036
+
1037
+ ## PEST Assertions Reference
1038
+
1039
+ ### HTTP Assertions
1040
+
1041
+ ```php
1042
+ $response->assertOk(); // 200
1043
+ $response->assertCreated(); // 201
1044
+ $response->assertNoContent(); // 204
1045
+ $response->assertNotFound(); // 404
1046
+ $response->assertUnprocessable(); // 422
1047
+ $response->assertUnauthorized(); // 401
1048
+ $response->assertForbidden(); // 403
1049
+ ```
1050
+
1051
+ ### JSON Assertions
1052
+
1053
+ ```php
1054
+ $response->assertJsonStructure(['data' => ['id', 'email']]);
1055
+ $response->assertJsonPath('data.email', 'test@example.com');
1056
+ $response->assertJsonCount(10, 'data');
1057
+ $response->assertJsonValidationErrors(['email', 'password']);
1058
+ ```
1059
+
1060
+ ### Database Assertions
1061
+
1062
+ ```php
1063
+ $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
1064
+ $this->assertDatabaseMissing('users', ['id' => $user->id]);
1065
+ $this->assertDatabaseCount('users', 5);
1066
+ ```
1067
+
1068
+ ### PEST Expectations
1069
+
1070
+ ```php
1071
+ expect($value)->toBe('expected');
1072
+ expect($value)->toBeTrue();
1073
+ expect($value)->toBeFalse();
1074
+ expect($value)->toBeNull();
1075
+ expect($value)->toBeEmpty();
1076
+ expect($value)->toHaveCount(5);
1077
+ expect($value)->toContain('item');
1078
+ expect($value)->toMatchArray(['key' => 'value']);
1079
+ ```
1080
+
1081
+ ---
1082
+
1083
+ ## Test Data Creation Strategy
1084
+
1085
+ ### Golden Rule: Use API to Create Data
1086
+
1087
+ **Create test data through the API whenever possible** - this ensures data passes through the same validation and business logic as real users.
1088
+
1089
+ ```php
1090
+ // ❌ BAD: Create directly with factory (bypasses validation)
1091
+ it('can update user', function () {
1092
+ $user = User::factory()->create([
1093
+ 'email' => 'invalid', // Factory allows invalid data!
1094
+ ]);
1095
+ // Test may pass but real API would reject this
1096
+ });
1097
+
1098
+ // ✅ GOOD: Create via API (same flow as real users)
1099
+ it('can update user', function () {
1100
+ // Create user through API
1101
+ $createResponse = $this->postJson('/api/users', validUserData());
1102
+ $createResponse->assertCreated();
1103
+
1104
+ $userId = $createResponse->json('data.id');
1105
+
1106
+ // Now test update
1107
+ $updateResponse = $this->putJson("/api/users/{$userId}", [
1108
+ 'name_lastname' => 'Updated',
1109
+ ]);
1110
+
1111
+ $updateResponse->assertOk();
1112
+ });
1113
+ ```
1114
+
1115
+ ### When to Use Factory vs API
1116
+
1117
+ | Scenario | Use Factory | Use API |
1118
+ | ------------------------------- | --------------------------- | ------------------------------- |
1119
+ | **Testing the endpoint itself** | ❌ | ✅ |
1120
+ | **Creating prerequisite data** | ⚠️ OK but be careful | ✅ Preferred |
1121
+ | **Testing relationships** | ✅ OK for related models | ✅ When testing the relation API |
1122
+ | **Performance (many records)** | ✅ Faster | ❌ Too slow |
1123
+ | **Testing edge cases** | ✅ Can create invalid states | ❌ API will reject |
1124
+
1125
+ ### Recommended Pattern
1126
+
1127
+ ```php
1128
+ describe('PUT /api/users/{id}', function () {
1129
+
1130
+ // Use API to create the user we'll update
1131
+ beforeEach(function () {
1132
+ $response = $this->postJson('/api/users', validUserData([
1133
+ 'email' => 'original@example.com',
1134
+ ]));
1135
+ $response->assertCreated();
1136
+
1137
+ $this->testUser = $response->json('data');
1138
+ });
1139
+
1140
+ it('updates user with valid data', function () {
1141
+ $response = $this->putJson("/api/users/{$this->testUser['id']}", [
1142
+ 'name_lastname' => 'NewName',
1143
+ ]);
1144
+
1145
+ $response->assertOk()
1146
+ ->assertJsonPath('data.name_lastname', 'NewName');
1147
+ });
1148
+
1149
+ it('fails with duplicate email', function () {
1150
+ // Create another user via API
1151
+ $this->postJson('/api/users', validUserData([
1152
+ 'email' => 'other@example.com',
1153
+ ]))->assertCreated();
1154
+
1155
+ // Try to update to existing email
1156
+ $response = $this->putJson("/api/users/{$this->testUser['id']}", [
1157
+ 'email' => 'other@example.com',
1158
+ ]);
1159
+
1160
+ $response->assertUnprocessable()
1161
+ ->assertJsonValidationErrors(['email']);
1162
+ });
1163
+ });
1164
+ ```
1165
+
1166
+ ### Factory Usage Guidelines
1167
+
1168
+ **When Factory is OK:**
1169
+
1170
+ ```php
1171
+ // ✅ OK: Creating many records for list/pagination tests
1172
+ it('returns paginated users', function () {
1173
+ User::factory()->count(25)->create(); // OK - testing pagination, not user creation
1174
+
1175
+ $response = $this->getJson('/api/users?per_page=10');
1176
+
1177
+ $response->assertOk()
1178
+ ->assertJsonCount(10, 'data');
1179
+ });
1180
+
1181
+ // ✅ OK: Creating related data for relationship tests
1182
+ it('returns user with posts', function () {
1183
+ $user = User::factory()
1184
+ ->has(Post::factory()->count(3))
1185
+ ->create();
1186
+
1187
+ $response = $this->getJson("/api/users/{$user->id}?include=posts");
1188
+
1189
+ $response->assertOk()
1190
+ ->assertJsonCount(3, 'data.posts');
1191
+ });
1192
+ ```
1193
+
1194
+ **When Factory is DANGEROUS:**
1195
+
1196
+ ```php
1197
+ // ❌ DANGEROUS: Testing validation with factory-created data
1198
+ it('updates user', function () {
1199
+ // Factory may create data that doesn't match real validation rules!
1200
+ $user = User::factory()->create();
1201
+
1202
+ // This test doesn't prove the API works correctly
1203
+ });
1204
+ ```
1205
+
1206
+ ---
1207
+
1208
+ ## Debugging Test Failures
1209
+
1210
+ ### Test Failed - Now What?
1211
+
1212
+ When a test fails, you MUST determine the root cause:
1213
+
1214
+ | Root Cause | Description | Action |
1215
+ | ----------------------------------- | --------------------------------- | ------------------------------------------- |
1216
+ | **Code Bug** | Actual application code is broken | Fix the application code |
1217
+ | **Test Bug** | Test code is incorrect | Fix the test |
1218
+ | **Business Logic Misunderstanding** | Test doesn't match requirements | Clarify requirements, then fix test or code |
1219
+ | **Environment Issue** | Database, config, timing issue | Fix environment/setup |
1220
+
1221
+ ### Debugging Checklist
1222
+
1223
+ ```mermaid
1224
+ flowchart TD
1225
+ A[TEST FAILED] --> B[1. Read error message carefully]
1226
+ B --> C[2. Check test code]
1227
+ C --> D{Is test code correct?}
1228
+
1229
+ D -->|NO| E[Fix test code]
1230
+ D -->|YES| F[Check actual code]
1231
+
1232
+ F --> G{Is it a business logic issue?}
1233
+
1234
+ G -->|YES| H[Clarify requirements]
1235
+ G -->|NO| I[Fix code bug]
1236
+
1237
+ C -.-> C1[Correct endpoint?]
1238
+ C -.-> C2[Correct data?]
1239
+ C -.-> C3[Correct assertion?]
1240
+ ```
1241
+
1242
+ ### Debugging Techniques
1243
+
1244
+ #### 1. Print Response Data
1245
+
1246
+ ```php
1247
+ it('creates user', function () {
1248
+ $response = $this->postJson('/api/users', validUserData());
1249
+
1250
+ // Debug: Print full response
1251
+ dump($response->json());
1252
+ dump($response->status());
1253
+
1254
+ $response->assertCreated();
1255
+ });
1256
+ ```
1257
+
1258
+ #### 2. Check Database State
1259
+
1260
+ ```php
1261
+ it('creates user', function () {
1262
+ $response = $this->postJson('/api/users', validUserData([
1263
+ 'email' => 'test@example.com',
1264
+ ]));
1265
+
1266
+ // Debug: Check what's actually in database
1267
+ dump(User::where('email', 'test@example.com')->first());
1268
+ dump(User::count());
1269
+
1270
+ $response->assertCreated();
1271
+ });
1272
+ ```
1273
+
1274
+ #### 3. Check Validation Errors
1275
+
1276
+ ```php
1277
+ it('creates user', function () {
1278
+ $response = $this->postJson('/api/users', validUserData());
1279
+
1280
+ // If 422, check which fields failed
1281
+ if ($response->status() === 422) {
1282
+ dump($response->json('errors'));
1283
+ }
1284
+
1285
+ $response->assertCreated();
1286
+ });
1287
+ ```
1288
+
1289
+ #### 4. Isolate the Problem
1290
+
1291
+ ```php
1292
+ // Break down complex test into smaller parts
1293
+ it('debug: check data is valid', function () {
1294
+ $data = validUserData();
1295
+ dump($data);
1296
+
1297
+ // Check each field manually
1298
+ expect($data['email'])->toContain('@');
1299
+ expect(strlen($data['password']))->toBeGreaterThanOrEqual(8);
1300
+ });
1301
+
1302
+ it('debug: check API accepts data', function () {
1303
+ $response = $this->postJson('/api/users', validUserData());
1304
+ dump($response->status());
1305
+ dump($response->json());
1306
+ });
1307
+ ```
1308
+
1309
+ ### Common Failure Patterns
1310
+
1311
+ #### Pattern 1: Test Passes Locally, Fails in CI
1312
+
1313
+ ```php
1314
+ // ❌ Problem: Depends on database state
1315
+ it('gets user', function () {
1316
+ $response = $this->getJson('/api/users/1'); // ID 1 may not exist!
1317
+ $response->assertOk();
1318
+ });
1319
+
1320
+ // ✅ Solution: Create own test data
1321
+ it('gets user', function () {
1322
+ $user = User::factory()->create();
1323
+ $response = $this->getJson("/api/users/{$user->id}");
1324
+ $response->assertOk();
1325
+ });
1326
+ ```
1327
+
1328
+ #### Pattern 2: Test Depends on Order
1329
+
1330
+ ```php
1331
+ // ❌ Problem: Tests affect each other
1332
+ it('creates user', function () {
1333
+ $this->postJson('/api/users', ['email' => 'test@example.com']);
1334
+ });
1335
+
1336
+ it('fails with duplicate email', function () {
1337
+ // This only works if previous test ran first!
1338
+ $this->postJson('/api/users', ['email' => 'test@example.com'])
1339
+ ->assertUnprocessable();
1340
+ });
1341
+
1342
+ // ✅ Solution: Each test is independent
1343
+ it('fails with duplicate email', function () {
1344
+ // Create first user in THIS test
1345
+ $this->postJson('/api/users', validUserData(['email' => 'test@example.com']))
1346
+ ->assertCreated();
1347
+
1348
+ // Now test duplicate
1349
+ $this->postJson('/api/users', validUserData(['email' => 'test@example.com']))
1350
+ ->assertUnprocessable();
1351
+ });
1352
+ ```
1353
+
1354
+ #### Pattern 3: Wrong Assertion
1355
+
1356
+ ```php
1357
+ // ❌ Problem: Asserting wrong thing
1358
+ it('creates user', function () {
1359
+ $response = $this->postJson('/api/users', validUserData());
1360
+
1361
+ // Wrong: assertOk is 200, but POST returns 201
1362
+ $response->assertOk();
1363
+ });
1364
+
1365
+ // ✅ Solution: Use correct assertion
1366
+ it('creates user', function () {
1367
+ $response = $this->postJson('/api/users', validUserData());
1368
+
1369
+ // Correct: POST returns 201 Created
1370
+ $response->assertCreated();
1371
+ });
1372
+ ```
1373
+
1374
+ #### Pattern 4: Test Data Doesn't Match Validation
1375
+
1376
+ ```php
1377
+ // ❌ Problem: Test data is invalid
1378
+ function validUserData(array $overrides = []): array {
1379
+ return array_merge([
1380
+ 'name_kana_lastname' => 'たなか', // Wrong! Must be Katakana
1381
+ ], $overrides);
1382
+ }
1383
+
1384
+ // ✅ Solution: Match validation rules exactly
1385
+ function validUserData(array $overrides = []): array {
1386
+ return array_merge([
1387
+ 'name_kana_lastname' => 'タナカ', // Correct: Katakana
1388
+ ], $overrides);
1389
+ }
1390
+ ```
1391
+
1392
+ ### When Test Fails, Ask These Questions
1393
+
1394
+ 1. **Is the test testing the right thing?**
1395
+ - Does the endpoint exist?
1396
+ - Is the HTTP method correct?
1397
+ - Are we asserting the right status code?
1398
+
1399
+ 2. **Is the test data correct?**
1400
+ - Does it match validation rules?
1401
+ - Are required fields present?
1402
+ - Are formats correct (email, date, etc.)?
1403
+
1404
+ 3. **Is the test isolated?**
1405
+ - Does it depend on other tests?
1406
+ - Does it depend on existing database data?
1407
+ - Does it clean up after itself?
1408
+
1409
+ 4. **Is this actually a bug?**
1410
+ - Check the business requirements
1411
+ - Maybe the code is correct and test is wrong
1412
+ - Maybe requirements changed
1413
+
1414
+ ---
1415
+
1416
+ ## Best Practices
1417
+
1418
+ ### DO
1419
+
1420
+ ```php
1421
+ // ✅ Use describe() to group related tests
1422
+ describe('POST /api/users', function () {
1423
+ it('正常: creates user with valid data', function () { ... });
1424
+ it('異常: fails with invalid email', function () { ... });
1425
+ });
1426
+
1427
+ // ✅ Use helper functions for test data
1428
+ function validUserData(array $overrides = []): array {
1429
+ return array_merge([...], $overrides);
1430
+ }
1431
+
1432
+ // ✅ Use API to create test data when testing mutations
1433
+ it('正常: updates user', function () {
1434
+ $createResponse = $this->postJson('/api/users', validUserData());
1435
+ $userId = $createResponse->json('data.id');
1436
+
1437
+ $this->putJson("/api/users/{$userId}", ['name' => 'New'])
1438
+ ->assertOk();
1439
+ });
1440
+
1441
+ // ✅ Use PEST expectations for cleaner assertions
1442
+ expect($response->json('data.email'))->toBe('test@example.com');
1443
+
1444
+ // ✅ Check database state after mutations
1445
+ $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
1446
+ ```
1447
+
1448
+ ### DON'T
1449
+
1450
+ ```php
1451
+ // ❌ Hardcode IDs
1452
+ $this->getJson('/api/users/1');
1453
+
1454
+ // ❌ Skip abnormal cases (異常系)
1455
+ // Only testing happy path (正常系) is NOT enough!
1456
+
1457
+ // ❌ Test multiple things in one test
1458
+ it('does everything', function () { ... }); // Too broad
1459
+
1460
+ // ❌ Depend on database state from other tests
1461
+ // Tests should be isolated
1462
+
1463
+ // ❌ Assume test failure = code bug
1464
+ // Always analyze: Is it test bug? Code bug? Business logic misunderstanding?
1465
+
1466
+ // ❌ Create test data that bypasses validation
1467
+ User::factory()->create(['email' => 'invalid']); // API would reject this!
1468
+
1469
+ // ❌ Test without understanding requirements
1470
+ // Read the spec first, then write tests
1471
+
1472
+ // ❌ Copy-paste tests without understanding
1473
+ // Each test should test ONE specific scenario
1474
+ ```
1475
+
1476
+ ---
1477
+
1478
+ ## Test Failure Analysis Checklist
1479
+
1480
+ Before fixing a failed test, answer these questions:
1481
+
1482
+ | Question | If Yes |
1483
+ | ----------------------------------------------------- | ------------------------------------- |
1484
+ | Is the test code correct? (endpoint, data, assertion) | Check application code |
1485
+ | Is the test data valid according to business rules? | Fix test data |
1486
+ | Does the test depend on other tests or data? | Make test independent |
1487
+ | Is the assertion correct for this endpoint? | Fix assertion (e.g., 201 not 200) |
1488
+ | Did the requirements change? | Update test to match new requirements |
1489
+ | Is this actually expected behavior? | Test is correct, code is a bug |
1490
+
1491
+ **Remember**: A failing test is information. Analyze it carefully before "fixing" anything.
1492
+
1493
+ ---
1494
+
1495
+ ## Summary
1496
+
1497
+ | Test Type | What to Test | When |
1498
+ | --------------------- | ------------------------------ | ---------------- |
1499
+ | **Normal (正常系)** | Happy path, expected behavior | Always |
1500
+ | **Abnormal (異常系)** | Errors, validation, edge cases | Always |
1501
+ | **Unit** | Pure logic, no HTTP | Complex services |
1502
+ | **Feature** | Full HTTP cycle | Every endpoint |
1503
+
1504
+ **Rule**: No endpoint is complete without both normal AND abnormal test cases.