🏖️

Laravel テスト用メソッドの assertRedirect を地味に拡張してみる

2022/01/06に公開

前置き

Laravel Ver. 8.75 で、テスト用メソッドの assertRedirect() も改善され、ステータスコードを調べる assertXxxx 系は、$this->withoutExceptionHandling(); を呼び出さなくてもエラー(例外)を自動で出力してくれるようになりました(出揃いました)。

ただ、assertRedirect() は、まだ未完成な部分があるので、無理矢理対応させて見る事にしました。(ほぼ自己満足の世界か…)

参考:[8.x] Display exception message with assertRedirect #39841
参考:Laravel 8.51 で、テスト時の withoutExceptionHandling() の呼び出しが、ほぼほぼ不要になった

問題点(改善点)

assertRedirect() は、第1引数に URL を指定して、その URL にリダイレクトされるかをテストする事ができます。その URL にリダイレクトされない場合、例えば、以下のように出力されます。(要点部分のみ)

  --- Expected
  +++ Actual
  @@ @@
  -'http://localhost/contact/confirm'
  +'http://localhost'

これだと何を間違ったのか、具体的には分かりません。
少なくとも私の場合は、99% バリデーションエラーが発生していて、元の URL に転送されようとしているので、期待するリダイレクト先の URL と一致しなかったりします。
dumpSession() を挟んで、以下みたいにやれば、原因が分かったりするのですが、それが地味に面倒なのですね。

$this->post(...)->dumpSession()->assertRedirect(...);

そこで、下で紹介するメソッドで元のメソッドをオーバーライドすれば、以下の感じで問題箇所を指摘してくれます。(要点部分のみ)

  Expected URL: http://localhost/contact/confirm
  Actual URL  : http://localhost

  Expected response status code [201, 301, 302, 303, 307, 308] but received 302.

  The following errors occurred during the request:

  会社名は、必須です。
  メールアドレスが一致しません。

これで問題箇所が一目瞭然です。

英語部分の「Expected response status code [201, 301, 302, 303, 307, 308] but received 302.」は、完全におかしなメッセージになってたりもしますが、そこは気にしない事にしておきます。

また、例によって問題行の指摘は無くなり、拡張部分の指摘になったりもしますが、そこも目をつむります…。

php artisan test

     98▕         // 確認画面へ
  ➜  99▕         $this->post(~~~, $valid)->assertRedirect(~~~);

ではなく、こんな感じ

     89▕         if ($expectedUri !== $actualUri) {
  ➜  90▕             PHPUnit::fail($returnMessage);

以下、拡張用のメソッドです。

以前の記事の tests/MyTestResponse.php に以下のメソッドを追加して下さい。

    public function assertRedirect($uri = null)
    {
        $detailMessage = $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $actual = $this->getStatusCode());

        if ($actual === 500) {
            PHPUnit::fail($detailMessage);
        }

        PHPUnit::assertTrue(
            $this->isRedirect(), 'Response status code ['.$this->getStatusCode().'] is not a redirect status code.'
        );

        if (is_null($uri)) {
            return $this;
        }

        $expectedUri = app('url')->to($uri);
        $actualUri = app('url')->to($this->headers->get('Location'));

        $returnMessage = <<<EOT
        Expected URL: $expectedUri
        Actual URL  : $actualUri

        $detailMessage
        EOT;

        if ($expectedUri !== $actualUri) {
            PHPUnit::fail($returnMessage);
        }

        return $this;
    }

雑感

これで少し楽できます。

問題箇所等ありましたらコメント下さい。

Discussion