FormRequestクラスをテストする

## 動機

PHP/Laravelを触り始めて間もないが、HTTPリクエストのバリデーションで[FormRequest](https://readouble.com/laravel/8.x/ja/validation.html#form-request-validation)が便利ということで使っていた。手軽だったので[HTTPテスト](https://readouble.com/laravel/8.x/ja/http-tests.html)でテストを書いていたが、非効率な箇所もあったのでFormRequest単体でのテストを書きたかった。

## 前提

- PHPUnit 9.5.2
- Laravel Framework 8.27.0

## 内容

### テスト対象のクラスを定義する

```sh
$ php artisan make:request ExampleRequest
```

*authorize()* で真を返すようにし、 *rules()* で適当なバリデーションルールを追加する。

```php:app/Http/Requests/ExampleRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ExampleRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'id' => 'required',
            'name' => 'required',
            'email' => 'required'
        ];
    }
}
```

### テストを書く

テストファイルを作成する。（UnitかFeatureかの議論はここでは取り扱わない）

```sh
$ php artisan make:test ExampleTest
```

方法は以下の通り。 *Table Driven Test* 風に書きたかったので、データプロバイダを定義している。

- ExampleTestインスタンスを作成する
- Validatorインスタンスを作成する。テストデータをテスト関数の引数から、ルールとメッセージはExampleTestインスタンスから渡す
- バリデータを実行し、その結果を評価する

```php:tests/Feature/ExampleTest.php
<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
use App\Http\Requests\ExampleRequest;

class ExampleTest extends TestCase
{
    /**
     * @dataProvider validationProvider
     * @return void
     */
    public function testValidation($inData, $outFail, $outMessage)
    {
        $request = new ExampleRequest();
        $rules = $request->rules();
        $messages = $request->messages();
        $validator = Validator::make($inData, $rules, $messages);
        $result = $validator->fails();
        $this->assertEquals($outFail, $result);
        $messages = $validator->errors()->getMessages();
        $this->assertEquals($outMessage, $messages);
    }

    public function validationProvider()
    {
        return [
            'success' => [
                [
                    'id' => 1,
                    'name' => 'aaa',
                    'email' => 'aaaa@example.com',
                ],
                false,
                [],
            ],
            'empty all fields' => [
                [],
                true,
                [
                    'id' => ['The id field is required.'],
                    'name' => ['The name field is required.'],
                    'email' => ['The email field is required.'],
                ],
            ],
        ];
    }
}
```

## 注意点

HTTPテストをやめて書く場合、当然ながらそのテストで確認していた点はテストできないので注意（例えば、FormRequestが返すメッセージ構造とHTTPレスポンスの構造は異なるなど。）

## 参考

- [FormRequest](https://readouble.com/laravel/8.x/ja/validation.html#form-request-validation)
- [HTTPテスト](https://readouble.com/laravel/8.x/ja/http-tests.html)
- [データプロバイダ](https://phpunit.readthedocs.io/ja/latest/writing-tests-for-phpunit.html#writing-tests-for-phpunit-data-providers)
- [【Laravel】フォームリクエストバリデーションのテストコード作成](https://qiita.com/n_mogi/items/57a946205df2a69889c2)