# 機能設計書：テストサポート

## 1. 機能概要

### 1.1 機能名
テストサポート（Testing Support）

### 1.2 機能説明
Laravel アプリケーションのテストを支援する機能群。HTTP レスポンスのアサーション、データベーステスト、モック機能、Fluent JSON アサーションなどを提供し、PHPUnit と統合された直感的なテスト記述を可能にする。

### 1.3 関連画面
- なし（テスト専用機能）

## 2. 機能詳細

### 2.1 処理フロー

```
[テストケース]
    |
    v
[HTTP リクエスト送信]
    |
    v
[TestResponse 取得]
    |
    v
[アサーション実行]
    |
    +-- assertStatus()
    +-- assertJson()
    +-- assertSee()
    +-- assertRedirect()
    +-- etc.
    |
    v
[テスト結果]
```

### 2.2 クラス構成

| クラス名 | 役割 |
|---------|------|
| TestResponse | HTTP レスポンスのアサーション |
| TestCase | テストケース基底クラス |
| AssertableJson | Fluent JSON アサーション |
| Concerns/AssertsStatusCodes | ステータスコードアサーション |
| Constraints/SeeInOrder | 順序付き文字列チェック |

### 2.3 主要メソッド

#### TestResponse - ステータスアサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertStatus()` | ステータスコード確認 | `$status` | `$this` |
| `assertSuccessful()` | 成功（2xx）確認 | - | `$this` |
| `assertOk()` | 200 OK 確認 | - | `$this` |
| `assertCreated()` | 201 Created 確認 | - | `$this` |
| `assertNoContent()` | 204 No Content 確認 | - | `$this` |
| `assertNotFound()` | 404 Not Found 確認 | - | `$this` |
| `assertForbidden()` | 403 Forbidden 確認 | - | `$this` |
| `assertUnauthorized()` | 401 Unauthorized 確認 | - | `$this` |
| `assertClientError()` | クライアントエラー（4xx）確認 | - | `$this` |
| `assertServerError()` | サーバーエラー（5xx）確認 | - | `$this` |

#### TestResponse - コンテンツアサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertSee()` | 文字列存在確認 | `$value, $escape` | `$this` |
| `assertDontSee()` | 文字列非存在確認 | `$value, $escape` | `$this` |
| `assertSeeInOrder()` | 順序付き文字列確認 | `$values, $escape` | `$this` |
| `assertSeeText()` | テキスト存在確認 | `$value, $escape` | `$this` |
| `assertJson()` | JSON構造確認 | `$data, $strict` | `$this` |
| `assertJsonPath()` | JSONパス値確認 | `$path, $expect` | `$this` |
| `assertJsonFragment()` | JSON部分一致確認 | `$data` | `$this` |
| `assertJsonMissing()` | JSON非存在確認 | `$data` | `$this` |
| `assertJsonStructure()` | JSON構造確認 | `$structure, $responseData` | `$this` |
| `assertJsonCount()` | JSON要素数確認 | `$count, $key` | `$this` |

#### TestResponse - リダイレクトアサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertRedirect()` | リダイレクト確認 | `$uri` | `$this` |
| `assertRedirectToRoute()` | ルートへのリダイレクト確認 | `$name, $parameters` | `$this` |
| `assertRedirectBack()` | 前ページへのリダイレクト確認 | - | `$this` |
| `assertRedirectContains()` | リダイレクト先に文字列含む | `$uri` | `$this` |

#### TestResponse - ヘッダー・Cookie アサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertHeader()` | ヘッダー確認 | `$headerName, $value` | `$this` |
| `assertHeaderMissing()` | ヘッダー非存在確認 | `$headerName` | `$this` |
| `assertCookie()` | Cookie確認 | `$cookieName, $value` | `$this` |
| `assertCookieMissing()` | Cookie非存在確認 | `$cookieName` | `$this` |

#### TestResponse - セッションアサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertSessionHas()` | セッション値存在確認 | `$key, $value` | `$this` |
| `assertSessionMissing()` | セッション値非存在確認 | `$key` | `$this` |
| `assertSessionHasErrors()` | エラー存在確認 | `$keys, $format, $errorBag` | `$this` |
| `assertSessionHasNoErrors()` | エラー非存在確認 | - | `$this` |
| `assertSessionHasInput()` | 入力値存在確認 | `$key, $value` | `$this` |

#### TestResponse - ビューアサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `assertViewIs()` | ビュー名確認 | `$value` | `$this` |
| `assertViewHas()` | ビューデータ確認 | `$key, $value` | `$this` |
| `assertViewMissing()` | ビューデータ非存在確認 | `$key` | `$this` |

#### AssertableJson - Fluent JSON アサーション

| メソッド | 説明 | 引数 | 戻り値 |
|---------|------|------|--------|
| `has()` | キー存在確認 | `$key, $length, $callback` | `$this` |
| `hasAll()` | 複数キー存在確認 | `$keys` | `$this` |
| `missing()` | キー非存在確認 | `$key` | `$this` |
| `where()` | 値の確認 | `$key, $expected` | `$this` |
| `whereType()` | 型の確認 | `$key, $expected` | `$this` |
| `first()` | 最初の要素を検証 | `$callback` | `$this` |
| `each()` | 各要素を検証 | `$callback` | `$this` |
| `count()` | 要素数確認 | `$key, $length` | `$this` |

### 2.4 ビジネスルール

1. **チェーンアサーション**
   - 各アサーションは `$this` を返却
   - メソッドチェーンで複数アサーション可能

2. **失敗時の動作**
   - アサーション失敗時は PHPUnit の例外をスロー
   - 詳細なエラーメッセージを提供

3. **Fluent JSON**
   - `assertJson()` にクロージャを渡すと Fluent モード
   - ネストした構造も直感的に検証可能

4. **エスケープ処理**
   - `assertSee()` 等はデフォルトで HTML エスケープ
   - `$escape = false` で無効化可能

## 3. 入出力仕様

### 3.1 入力

| 項目 | 型 | 説明 |
|-----|-----|------|
| response | `Response` | HTTP レスポンス |
| request | `Request` | 元のリクエスト（オプション） |

### 3.2 出力

| 項目 | 型 | 説明 |
|-----|-----|------|
| TestResponse | `TestResponse` | アサーション可能なレスポンス |

## 4. 依存関係

### 4.1 内部依存

| パッケージ | 用途 |
|-----------|------|
| Illuminate\Support\Arr | 配列操作 |
| Illuminate\Support\Str | 文字列操作 |
| Illuminate\Support\Collection | コレクション |
| Illuminate\Testing\Fluent\AssertableJson | Fluent JSON |

### 4.2 外部依存

| パッケージ | 用途 |
|-----------|------|
| phpunit/phpunit | テストフレームワーク |
| symfony/http-foundation | HTTP基盤 |

## 5. エラー処理

| エラー種別 | 発生条件 | 処理内容 |
|-----------|---------|---------|
| AssertionFailedError | アサーション失敗 | PHPUnit 例外 |
| LogicException | 不正なメソッド呼び出し | 例外スロー |

## 6. コードリーディングガイド

### 6.1 推奨読解順序

1. **TestResponse クラス** (`Testing/TestResponse.php`)
   - **36-41行目**: クラス定義とトレイト使用
   - **47-54行目**: プロパティ（baseRequest, baseResponse, exceptions）
   - **76-81行目**: コンストラクタ
   - **92-95行目**: `fromBaseResponse()` - 静的ファクトリ

2. **ステータスアサーション**
   - **102-110行目**: `assertSuccessful()` - 2xx確認
   - **117-133行目**: `assertSuccessfulPrecognition()` - Precognition確認
   - **140-148行目**: `assertClientError()` - 4xx確認
   - **155-163行目**: `assertServerError()` - 5xx確認
   - **171-178行目**: `assertStatus()` - 任意ステータス確認

3. **リダイレクトアサーション**
   - **198-210行目**: `assertRedirect()` - リダイレクト確認
   - **218-230行目**: `assertRedirectContains()` - 部分一致
   - **237-247行目**: `assertRedirectBack()` - 前ページ確認
   - **287-299行目**: `assertRedirectToRoute()` - ルート確認

4. **コンテンツアサーション**
   - （後続行）: `assertSee()`, `assertJson()` 等

### 6.2 プログラム呼び出し階層図

```
$response = $this->get('/users');
└── TestResponse::fromBaseResponse()
    └── new TestResponse($response)

$response->assertOk()
└── AssertsStatusCodes::assertOk()
    └── $this->assertStatus(200)
        └── PHPUnit::assertSame(200, $actual)

$response->assertJson(['key' => 'value'])
└── TestResponse::assertJson()
    ├── 配列の場合: PHPUnit::assertArraySubset()
    └── Closure の場合: AssertableJson 経由
        └── AssertableJson::fromAssertableJsonString()
```

### 6.3 データフロー図

```
[HTTP テストリクエスト]
    |
    v
[Application::handle()]
    |
    v
[Symfony Response]
    |
    v
[TestResponse::fromBaseResponse()]
    |
    v
[TestResponse インスタンス]
    |
    +-- baseResponse: 元のレスポンス
    +-- baseRequest: 元のリクエスト
    +-- exceptions: 例外コレクション
    |
    v
[アサーションメソッド呼び出し]
    |
    +-- 成功: $this を返却（チェーン継続）
    +-- 失敗: AssertionFailedError スロー
```

### 6.4 読解のコツ

- **トレイト分離**: ステータスコードアサーションは `AssertsStatusCodes` トレイトに分離
- **PHPUnit 統合**: 内部で `PHPUnit\Framework\Assert` を使用
- **Macroable**: 独自アサーションを追加可能
- **Fluent パターン**: すべてのアサーションが `$this` を返却

### 6.5 関連ファイル一覧

| ファイルパス | 種別 | 役割 |
|------------|------|------|
| `src/Illuminate/Testing/TestResponse.php` | Response | メインレスポンスクラス |
| `src/Illuminate/Testing/TestResponseAssert.php` | Assert | PHPUnit連携 |
| `src/Illuminate/Testing/Concerns/AssertsStatusCodes.php` | Trait | ステータスアサーション |
| `src/Illuminate/Testing/Fluent/AssertableJson.php` | Json | Fluent JSONアサーション |
| `src/Illuminate/Testing/Constraints/SeeInOrder.php` | Constraint | 順序付きチェック |
| `src/Illuminate/Foundation/Testing/TestCase.php` | TestCase | テスト基底クラス |

## 7. 使用例

```php
use Tests\TestCase;

class UserControllerTest extends TestCase
{
    public function test_can_list_users()
    {
        // 基本的なアサーション
        $response = $this->get('/users');

        $response->assertOk()
            ->assertSee('Users')
            ->assertViewIs('users.index');
    }

    public function test_can_create_user()
    {
        $response = $this->post('/users', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
        ]);

        $response->assertCreated()
            ->assertJson([
                'name' => 'John Doe',
            ]);
    }

    public function test_validation_errors()
    {
        $response = $this->post('/users', []);

        $response->assertStatus(422)
            ->assertSessionHasErrors(['name', 'email']);
    }

    public function test_json_structure()
    {
        $response = $this->getJson('/api/users');

        $response->assertJsonStructure([
            'data' => [
                '*' => ['id', 'name', 'email'],
            ],
            'meta' => ['total', 'page'],
        ]);
    }

    public function test_fluent_json()
    {
        $response = $this->getJson('/api/users/1');

        $response->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                 ->where('name', 'John Doe')
                 ->whereType('email', 'string')
                 ->has('profile', fn ($json) =>
                     $json->where('bio', 'Developer')
                          ->etc()
                 )
                 ->etc()
        );
    }

    public function test_redirect()
    {
        $response = $this->post('/login', [
            'email' => 'user@example.com',
            'password' => 'password',
        ]);

        $response->assertRedirect('/dashboard')
            ->assertSessionHas('success');
    }
}
```

## 8. 改版履歴

| バージョン | 日付 | 変更内容 |
|-----------|------|---------|
| 1.0.0 | - | 初版作成 |
