FormRequestのユニットテスト2記事目の今回は、prepareForValidation()を含めてテストをしたい場合はどうするのか?についてご紹介します。前回に続いて、「店舗情報更新画面」のバリデーション・認可処理を行うShopUpdateRequestを例にテストを作成します。


前回の記事はこちらからどうぞ。

テスト対象

ShopUpdateRequestにはprepareForValidation()が定義されています。name要素の半角カタカナを全角カタカナに変換する、というものです。

・・・・・
    protected function prepareForValidation(): void
    {
        // 店舗名の半角カタカナを全角カタカナに変換
        $this->merge([
            'name' => mb_convert_kana($this->input('name') ?? '', 'KV'),
        ]);
    }
・・・・・

prepareForValidation()を含めたテスト

prepareForValidation()も含めてテストを行う場合、rules()単体のテストで使ったValidator::make()による検証ではprepareForValidation()は呼ばれないので、少し工夫する必要があります。

Laravel7の時に当サイトでご紹介したFormRequestのテストの記事で記載の$this->app->resolving…を使用した手法は現在も有効ですので、そちらを使って以下のようなテストを書くことができます。

    #[Test]
    #[DataProvider('validationWithKanaConversionDataProvider')]
    public function 店舗名の半角カタカナが全角カタカナに変換されること(array $input, string $expected): void
    {
        $input = array_merge(
            $input,
            [
                'description' => 'テスト用の説明文です',
                'active_flag' => 'Y'
            ],
        );

        $adminUser = User::factory()->create(['role' => 'admin']);
        $this->actingAs($adminUser);

        // インスタンス作成時に実行する処理を設定
        $this->app->resolving(ShopUpdateRequest::class, function ($resolved) use ($input) {
            $resolved->merge($input);
        });

        $request = app(ShopUpdateRequest::class);

        $this->assertEquals($expected, $request->input('name'));
    }

    public static function validationWithKanaConversionDataProvider(): array
    {
        return [
            '半角カタカナ' => [
                ['name' => 'テストショップ'],
                'テストショップ',
            ],
            '漢字混在' => [
                ['name' => 'テスト店舗'],
                'テスト店舗',
            ],
            '全角カタカナ' => [
                ['name' => 'テストショップ'],
                'テストショップ',
            ],
        ];
    }

app(ShopUpdateRequest::class)を実行した時点でprepareForValidation()だけでなくauthorize()rules()messages()全てが呼ばれます。なのでauthorize()を設定している場合は認証を通すための準備も必要になりますので、ご注意ください。

FormRequest全体をテストしながら、ルートパラメータも取得したい場合

先ほどのコードでFormRequestの各メソッドを通した全体的なテストが可能となりましたが、ルートパラメータを取得してのテストも同時に行いたい場合は追加の情報が必要となります。前回の記事内、rules()単体のテストの項でご紹介したように、リクエストインスタンスにはルート情報がセットされていないからでしたね。

先ほどのテストコードにあった$this->app->resolving()の中でルート情報をセットできるように編集します。setRouteResolver()を使って、以下のように記述します。

・・・・・
        $this->app->resolving(ShopUpdateRequest::class, function ($resolved) use ($input, $shop) {
            $resolved->merge($input);

            $route = Route::getRoutes()->match($resolved);
            $route->setParameter('shop', $shop);
            $resolved->setRouteResolver(fn() => $route);
        });

        $request = app(ShopUpdateRequest::class);
・・・・・

ただ、ここまでするのであればFeatureテストで書いたほうがシンプルなテストコードで書けるかもしれませんね。

包括的なテストとメソッド単体のテストまとめ

FormRequest全体を通したテストを行えば、より実際の動作に近い形の検証が可能です。ですがシンプルなFormRequestの時や、バリデーションに焦点を当てたテストを書きたい場合などは、より軽量な方法としてrules()autorize()単体のテストとするほうが良いと思いますので、FormRequestの内容に応じて使い分けたほうが良さそうです。

前回と今回の記事でご紹介したテストの書き方を簡単に以下にまとめましたので、FormRequestのテストを作成する際のご参考になれば嬉しいです。

テスト対象 テスト方法
authorize() $request->authorize() を直接呼び出す
rules() Validator::make($data, $request->rules(), $request->messages())
rules()(ルートパラメータ必要な場合) setRouteResolver() でルート情報を設定
prepareForValidation() を含めた検証 $this->app->resolving() + app(FormRequest::class)
prepareForValidation() + ルートパラメータ resolving() 内で setRouteResolver() でルート情報を設定
メルマガ購読の申し込みはこちらから。

By hmatsu