環境
- PHP: 8.1.5
- Laravel: 9.9.0
テストコード
<?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が発生しています。
フレームワークのコードでいうとこの部分です。
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
を確認します。
<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_ENV
に testing
が指定されているので良さそうに思います。
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_ENV
に local
が指定されているのでこちらが優先されているようです。
<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タグに戻して試します。
<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_ENV
に testing
以外の値が設定されている場合。
テスト実行時は APP_ENV
が testing
の時はCSRFトークンの例外は発生しないので、
phpunit.xml
の env
要素を server
要素に変更すると動く。
さいごに
どうしてこんなことになったのか、私にはわかりません。
これを読んだあなた。
どうか真相を暴いてください。
それだけが私の望みです。