13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel HTTPテストで419エラーが発生した時の対処法

Last updated at Posted at 2022-04-27

環境

  • PHP: 8.1.5
  • Laravel: 9.9.0

テストコード

tests/Feature/Http/Controllers/Auth/LoginControllerTest.php
<?php declare(strict_types=1);

namespace Tests\Feature\Http\Controllers\Auth;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

final class LoginControllerTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @return void
     */
    public function testSuccess()
    {
        User::factory()->create(['email' => 'test@example.com']);

        $response = $this->postJson('/login', [
            'email' => 'test@example.com',
            'password' => 'password',
        ]);

        $response->assertStatus(200);
        $response->assertJson([
            'data' => true,
            'message' => 'Login successful.',
        ]);
    }
}

ログインが成功するかどうかのテストを書いてます。

テストコードの実行結果

$ ./vendor/bin/phpunit

1) Tests\Feature\Http\Controllers\Auth\LoginControllerTest::testSuccess
Expected response status code [200] but received 419.
Failed asserting that 200 is identical to 419.

/data/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:178
/data/tests/Feature/Http/Controllers/Auth/LoginControllerTest.php:26

200が返ってきて欲しいところが419HTTPステータスコードが返ってきてしまっています。

念の為、キャッシュクリアする

テストを実行する前は必ず config:clear Artisanコマンドを使用して設定のキャッシュをクリアしてください。

$ php artisan config:clear

php artisan config:cache していなければ bootstrap/cache/config.php のキャッシュファイルも作られないので不要です。
念の為、実行しましましたが変化はありませんでした。

419 TokenMismatchException

419が返る時はTokenMismatchExceptionが発生しています。
フレームワークのコードでいうとこの部分です。

src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php
    public function handle($request, Closure $next)
    {
        if (
            $this->isReading($request) ||
            $this->runningUnitTests() ||
            $this->inExceptArray($request) ||
            $this->tokensMatch($request)
        ) {
            return tap($next($request), function ($response) use ($request) {
                if ($this->shouldAddXsrfTokenCookie()) {
                    $this->addCookieToResponse($request, $response);
                }
            });
        }

        throw new TokenMismatchException('CSRF token mismatch.');
    }

VerifyCsrfTokenミドルウェアでトークンが見つからなかったら例外を返しています。
テストコードに毎回Csrfトークンを仕込んだり、ミドルウェアを除外して対応するのは論外というか負けた気がしますね。

ここのコードを見て思うのは、 $this->runningUnitTests() あるからテスト時は例外発生しないのでは?

configの環境を確認するテスト

下記のコードを追加してテスト試します。

$this->assertEquals('testing', config('app.env'));
$ ./vendor/bin/phpunit

1) Tests\Feature\Http\Controllers\Auth\LoginControllerTest::testSuccess
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'testing'
+'local'

testing になって欲しいところが、 local になってます。

phpunit.xml

phpunit.xml を確認します。

phpunit.xml
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <env name="DB_DATABASE" value=":memory:"/> -->
        <env name="MAIL_MAILER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="TELESCOPE_ENABLED" value="false"/>
    </php>

APP_ENVtesting が指定されているので良さそうに思います。

Laravel 8.x と 9.x

https://github.com/laravel/laravel/blob/8.x/phpunit.xml
https://github.com/laravel/laravel/blob/9.x/phpunit.xml

server タグから env タグに変更されています。
変更されたプルリクは下記です。

自分の過去記事より

Laravel7系時点の記事ですが、server タグから env タグに変更して force 属性を付与していました。

$ echo $APP_ENV
local

サーバー環境変数の APP_ENVlocal が指定されているのでこちらが優先されているようです。

phpunit.xml
    <php>
        <env name="APP_ENV" value="testing" force="true"/>
    </php>

これで動くのでは?

$ ./vendor/bin/phpunit

1) Tests\Feature\Http\Controllers\Auth\LoginControllerTest::testSuccess
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'testing'
+'local'

動かない。

env から server タグに変更する

Laravel8系の頃のserverタグに戻して試します。

phpunit.xml
    <php>
        <server name="APP_ENV" value="testing"/>
    </php>
$ ./vendor/bin/phpunit 
PHPUnit 9.5.20 #StandWithUkraine

....                                                                4 / 4 (100%)

Time: 00:01.638, Memory: 30.00 MB

OK (4 tests, 6 assertions)

動いた...なぜ?

https://phpunit.readthedocs.io/ja/latest/configuration.html?highlight=server#php-ini
phpunitのドキュメントを見てもよくわからない...

結論

サーバー環境変数の APP_ENVtesting 以外の値が設定されている場合。

テスト実行時は APP_ENVtesting の時はCSRFトークンの例外は発生しないので、
phpunit.xmlenv 要素を server 要素に変更すると動く。

さいごに

どうしてこんなことになったのか、私にはわかりません。
これを読んだあなた。
どうか真相を暴いてください。
それだけが私の望みです。

13
7
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?