PHPUnitで行うValidationのテスト

実務でPHPUnitを使ってValidationの異常系テストコードを追加することがあったので
そこで得られた知識をアウトプットします！

仮に以下のRequestファイルがあったとします。

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

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:100',
            'email' => 'required'
        ];
    }
}
```
:::note warn
ユーザー名に**必須チェック**と**最大文字数**
メールアドレスに**必須チェック**
のバリデーションがついています。
:::

### テストコードの作成

バリデーションのチェックは、一つ一つチェックしていくこともできますが、
コードの保守性を考え、データプロバイダーを使用しました。

```php:tests/Unit/Requests/UserRequestTest.php
<?php

class UserRequestTest extends TestCase
{
	/* *
	 * バリデーションテスト
	 *
	 * @param array 項目名の配列
	 * @param array 値の配列
	 * @param 期待値(true:バリデーションOK、false:バリデーションNG)
	 *
	 * 使用するデータプロバイダーを記述
	 * @dataProvider dataprovider
	 */
	public function testBasicTest(array $keys, array $values, bool $expect): void
	{
		//入力項目の配列（$keys）と値の配列($values)から、連想配列を生成する
		$dataList = array_combine($keys, $values);

		//先ほど作成したUserRequestのオブジェクトを作成
		$request  = new UserRequest();

		//ユーザーリクエストで定義したルールを取得
		$rules    = $request->rules();

		//Validatorファサードでバリデーターのインスタンスを取得、その際に入力情報とバリデーションルールを引数で渡す
		$validator = Validator::make($dataList, $rules);

		//入力情報がバリデーショルールを満たしている場合はtrue、満たしていな場合はfalseが返る
		$result    = $validator->passes();

		//期待値($expect)と結果($result)を比較
		$this->assertEquals($expect, $result);
	}

	/**
	 * データプロバイダ
	 *
	 * @return データプロバイダ
	 *
	 * @dataProvider dataprovider
	 */
	public function dataprovider(): array
	{
		return [
			'ユーザー名が必須エラー' => [
				['name', 'email'],
				[null, 'test@example.com'],
				false
			],
			'ユーザー名が100文字超えているエラー' => [
				['name', 'email'],
				[str_repeat('あ', 101),'test@example.com'],
				false
			],
			'メールアドレスが必須エラー' => [
				['name', 'email'],
				['test', null],
				false
			],
		];
	}
}

```

今回は異常系のテストだけを記述しましたが、
このように記述することで、複数のバリデーションでも見やすく、変更がしやすいテストを記述することができます！

### 余談
プロバイダーメソッドを使用してつまづいた実装をメモで残しておきます。

#### つまづいた点①

:::note warn
**ネストされた値をどうチェックするか**
user.nameのようにドットで項目が記載されている
:::

仮に以下のRequestファイルがあったとします。

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

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

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

今まで通りにデータプロバイダーを作成しても、正しい動きになりません。
実際に何時間も悩みました...

上記の場合は、以下のように書くと正しく動きます！
```php:tests/Unit/Requests/UserRequestTest.php
/**
	 * データプロバイダ
	 *
	 * @return データプロバイダ
	 *
	 * @dataProvider dataprovider
	 */
	public function dataprovider(): array
	{
		return [
			'ユーザー名が必須エラー' => [
				['user',],
				[['name' => null], ['email' => 'test@example.com']],
				false
			],
			'ユーザー名が100文字超えているエラー' => [
				['name', 'email'],
				[['name' => str_repeat('あ', 101)], ['email' => 'test@example.com']],
				false
			],
			'メールアドレスが必須エラー' => [
				['name', 'email'],
				[['name' => 'test'], ['email' => null]],
				false
			],
		];
	}
```

#### つまづいた点②

:::note warn
ログインユーザーIDが使われている
:::

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

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'id' => $this->user()->id,
            'name' => 'required|max:100',
            'email' => 'required'
        ];
    }
}
```
上記の例は、認証などが関わっており、そこで使用されていました。

この場合も、データプロバイダーに
```
Auth::id()
```
と書いても正しい動きにはなりません。
このような場合は、以下のように書く必要があります。

```php:tests/Unit/Requests/UserRequestTest.php
<?php

class UserRequestTest extends TestCase
{
	/* *
	 * バリデーションテスト
	 *
	 * @param array 項目名の配列
	 * @param array 値の配列
	 * @param 期待値(true:バリデーションOK、false:バリデーションNG)
	 *
	 * 使用するデータプロバイダーを記述
	 * @dataProvider dataprovider
	 */
	public function testBasicTest(array $keys, array $values, bool $expect): void
	{
		//入力項目の配列（$keys）と値の配列($values)から、連想配列を生成する
		$dataList = array_combine($keys, $values);

		//先ほど作成したUserRequestのオブジェクトを作成
		$request  = new UserRequest();

        // setUserResolverを呼ぶ
        $request = setUserResolver(function () use ($user) {
            return $user;
        }

		//ユーザーリクエストで定義したルールを取得
		$rules    = $request->rules();

		//Validatorファサードでバリデーターのインスタンスを取得、その際に入力情報とバリデーションルールを引数で渡す
		$validator = Validator::make($dataList, $rules);

		//入力情報がバリデーショルールを満たしている場合はtrue、満たしていな場合はfalseが返る
		$result    = $validator->passes();

		//期待値($expect)と結果($result)を比較
		$this->assertEquals($expect, $result);
	}

}

```

のようにしてあげると正しい挙動になる。

あくまで、自分の学習のメモとして残しているので、参考程度に見ていただけると幸いです。