Laravel:テストにおける認証の罠

Date:

Share post:

昨今、ちょいちょい引っ掛かったネタなので紹介しておきます。
Laravelのバージョンは「6.x」です。
7以降のバージョンで改善されていると良いのですが…

問題の現象

以下のような、ごく簡単なコントローラーを作成します。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

use Illuminate\Support\Facades\Auth;

class DummyController extends Controller
{
    public function __invoke()
    {
        if (Auth::user() === null) {
            return json_encode(['result' => 'FALSE']);
        }
        return json_encode(['result' => 'TRUE']);
    }
}

認証されたユーザー情報があれば文字列「TRUE」を返し、なければ「FALSE」を返します。

一方で、以下のようなテストケースを考えます。

<?php

namespace Tests\Feature;

use App\Models\User;

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class DummyTest extends TestCase
{
    public function test() : void
    {
        $UserModel = User::get()->random();

        $this->actingAs($UserModel, 'user');
        // 実行
        $response = $this->json('get', route('dummy'));

        // 確認
        $response->assertStatus(200);
        $response->assertExactJson([
            'result' => 'TRUE',
        ]);
    }
}

戻りが文字列「TRUE」であることを期待しています。

最後にルートの設定ですが、ここがポイントです。
まずは以下のようにします。

Route::get('dummy', 'DummyController')->name('dummy');

これで先のテストケースを実行すると正常に実行できます(できてしまいます)。
しかし、実際の処理において、認証(ログイン)後に当該アクションを実行すると文字列「FALSE」が返されます。

実際の挙動に関してはcurlを使って確認できます。
GETとは異なりPOSTにおいてはPOSTパラメータの設定が必要だったり、認証状態をセッションで管理しているため、個別のcurl実行間でセッションを保持できるようcookieの送受信が必要になる点に要注意です。

curl -X POST -c cookie.txt -H "Content-Type: application/json" -d '{"account":"taro", "password":"taro01"}' http://sample.com/api/login
curl -b cookie.txt http://sample.com/api/dummy

上記結果は「{“result”:”FALSE”}」です。

原因

この原因はルート設定において認証を行うようになっていないことにあります。
正しくは以下のように設定すると、上記実行結果が「{“result”:”TRUE”}」になります。

Route::middleware('auth:user')->get('dummy', 'DummyController')->name('dummy');

Eloquent「App\Models\User」は「Illuminate\Foundation\Auth\User(Authenticatable)」を継承するようになっていて、Middleware「auth」においては「guard」に「user」が指定された場合はApp\Models\User」を使用して認証チェックをするよう「config/auth.php」で定義してあります。
この認証チェックを通していないと、例えログイン(認証)後のアクセスでもAuthファサードでログインユーザーの情報を取得することはできないようです。

それならそれで良いのですが、ではphpunitの実行においても同じように振る舞ってもらわないと困るのですが?

蛇足ながら、先のテストケースにおいて認証済み状態を作り出している下記処理

$this->actingAs($UserModel, 'user');

を削除(コメントアウト)して確認したところ、ルート設定で認証チェックを行うように指定していない場合は「{“result”:”FALSE”}」が返され、認証チェックを行うようにしてあった場合は401(Unauthorized)が返されます。
これら結果に関しては納得できますが、つまりは「actingAs」を実行することによりルート設定に関わらずAuthファサードでログインユーザー情報が取得できるようになってしまうと言うことのようです。

総括

phpunitでの確認では問題なくても、実際の動作ではエラーとなる可能性があると言うのは大変困った状況です。
今のところそのようなケースとして個人的に認識しているのは本件だけなのが救いですが。

最初にも書きましたが、本件はあくまでバージョン「6.x」での状況なので、7以降のバージョンで改善されているかもしれません。
そこに期待したいところです。

余談ながら、2021年9月にリリース予定だった Laravel 9 のリリースが2022年1月に延期されたようですね。
基本的にはLTSを使用するようにしているので、9待ちで7,8には手を出さずにいました(よって、7以降の状況が分かっていない訳です)。

Laravel 9 がリリースされたら、まずは本件を確認してみたいと思ってます。

Related articles

EC-CUBE 4系のプラグイン開発について その...

前回、プラグインを一旦有効化させて管理...

EC-CUBE 4系のプラグイン開発について その...

以前から作成したいと考えていたのですが...

Laravel Filamentを使用した管理画面...

前回Breezeをインストールしたこと...

Laravel Filamentを使用した管理画面...

前回、filamentでのリソース作成...