🦔

Laravel、Feature テスト時、ドカンと出るエラーの表示順を入れ替えて、少し楽をする

2021/11/16に公開

本題

【追記 2022-02-14】
~~~ここから~~~
Laravel Ver.9.1で、一番知りたいエラーメッセージの表示が下部にも表示されるようになります。詳しくは、こちらの記事をご覧下さい。ですので、下記の記事は不要となりますが、参考までに残しておきます。Ver.9.1以降は、下記は適用させないで下さい。新機能がうまく機能しなくなります。
~~~ここまで~~~

以前記事にしましたが、Ver.8.51以降は、Featureテストで、assertOk() などでステータスコードをチェックしていれば、withoutExceptionHandling() を書かなくても、例外エラーがドカンと出てくれるようになりました。便利な世の中になりました。

ただ、これはすごく便利なのですが、私の中である事に気づきました。
「エラーの根本原因は一番上に書いてあるので、毎回毎回、マウスのホイールを6~7回回して、画面を上に移動しなければならない…」
怠け者の私には、これが微妙に苦痛でした。そこで、Stack trace の上下が入れ替わるものを書いてみました。以下がそれです。

tests/TestCase.php

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Log\Events\MessageLogged;
use Illuminate\Testing\LoggedExceptionCollection;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    protected function setUp(): void
    {
        parent::setUp();

        // エラーログを上下逆順にして出力
        $this->app->make('events')->listen(MessageLogged::class, function ($event) {
            if (! isset($event->context['exception'])) {
                return;
            }

            tap($this->app->make(LoggedExceptionCollection::class), function ($collection) {
                $collection->push(
                    implode("\n", array_reverse(explode("\n", $collection->last())))
                );
            });
        });
    }
}

これを付ける前と後でどのように出力が変わるか、記載しておきます。
(__PATH__とある箇所は、実際のパスが入ります)

Before

  • Tests\Feature\Http\Controllers\StationControllerTest > ○○○○のテスト
  Expected response status code [200] but received 500.

  The following exception occurred during the request:

  Error: Call to undefined function App\Http\Controllers\hoge() in __PATH__/app/Http/Controllers/StationController.php:31
  Stack trace:
  #0 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\Http\Controllers\StationController->list()
  #1 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\Routing\Controller->callAction()
  #2 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\Routing\ControllerDispatcher->dispatch()
  #3 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\Routing\Route->runController()
  #4 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Router.php(695): Illuminate\Routing\Route->run()
  #5 __PATH__/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\Routing\Router->Illuminate\Routing\{closure}()

(~~~ 略 ~~~)

  #50 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestCase->run()
  #51 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestSuite->run()
  #52 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestSuite->run()
  #53 __PATH__/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(670): PHPUnit\Framework\TestSuite->run()
  #54 __PATH__/vendor/phpunit/phpunit/src/TextUI/Command.php(143): PHPUnit\TextUI\TestRunner->run()
  #55 __PATH__/vendor/phpunit/phpunit/src/TextUI/Command.php(96): PHPUnit\TextUI\Command->run()
  #56 __PATH__/vendor/phpunit/phpunit/phpunit(92): PHPUnit\TextUI\Command::main()
  #57 {main}
  Failed asserting that 200 is identical to 500.

  at tests/Feature/Http/Controllers/StationControllerTest.php:30
     26▕     {
     27▕         Station::factory(20)->create();
     28▕
     29▕         $this->get('xxx/yyy')
  ➜  30▕             ->assertOk();
     31▕     }
     32▕
     33▕     /** @test */
     34▕     function △△△のテスト()

After

  • Tests\Feature\Http\Controllers\StationControllerTest > ○○○○のテスト
  Expected response status code [200] but received 500.

  The following exception occurred during the request:

  #57 {main}
  #56 __PATH__/vendor/phpunit/phpunit/phpunit(92): PHPUnit\TextUI\Command::main()
  #55 __PATH__/vendor/phpunit/phpunit/src/TextUI/Command.php(96): PHPUnit\TextUI\Command->run()
  #54 __PATH__/vendor/phpunit/phpunit/src/TextUI/Command.php(143): PHPUnit\TextUI\TestRunner->run()
  #53 __PATH__/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(670): PHPUnit\Framework\TestSuite->run()
  #52 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestSuite->run()
  #51 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestSuite->run()
  #50 __PATH__/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestCase->run()

(~~~ 略 ~~~)

  #5 __PATH__/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\Routing\Router->Illuminate\Routing\{closure}()
  #4 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Router.php(695): Illuminate\Routing\Route->run()
  #3 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\Routing\Route->runController()
  #2 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\Routing\ControllerDispatcher->dispatch()
  #1 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\Routing\Controller->callAction()
  #0 __PATH__/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\Http\Controllers\StationController->list()
  Stack trace:
  Error: Call to undefined function App\Http\Controllers\hoge() in __PATH__/app/Http/Controllers/StationController.php:31
  Failed asserting that 200 is identical to 500.

  at tests/Feature/Http/Controllers/StationControllerTest.php:30
     26▕     {
     27▕         Station::factory(20)->create();
     28▕
     29▕         $this->get('xxx/yyy')
  ➜  30▕             ->assertOk();
     31▕     }
     32▕
     33▕     /** @test */
     34▕     function △△△のテスト()

出力後は、一番下にいて、「Error: Call to undefined function App\Http\Controllers\hoge()」の箇所が、一番知りたい箇所なのですが、Beforeだと一番上の辺りにあり、移動が大変です。

一方、Afterの場合、すぐ下にあるので、移動せずとも一目瞭然です。これで開発も更に楽しめそうです。

ちなみに、After でも「Expected response status code [200] but received 500.」の箇所は、上にあるままです。Laravel本体を直してしまえば、これを下に持ってくるのも簡単なのですが、上記の方法だと、ちょっと無理ですね。

当初は、プルリクも検討したのですが、他にもエラー内容吐き出す箇所はあったりして、でもそっちは順番入れ替えると微妙だったり、などなど考えると、異論反論も多そうだなと思ったりして、ひとまず自分用に作成したという感じです。(ややハック的なやり方ですが)

ちなみに

ステータスコードを調べるアサーションは幾つかありますが、現在、assertRedirect() のみは、他と少しコードが異なる為、このエラー内容出力機能に対応してなく、従来通り、withoutExceptionHandling() を呼び出さないといけません。

それを直すべく、プルリクを送った方がいましたが、見事却下されています。
[8.x] Add exception message to assertRedirect #39236

ただ、このプルリク程修正をしなくても、もっと簡易(6~7行レベル)の修正で対応可能そうなので、また一段落したら、プルリク検討してみます。(まぁ、却下されるかも知れませんが)

雑感

できたてほやほやですので、もし不具合など見つけたら、コメント下さい。

Discussion