2024年10月29日
Livewireのmount()メソッド内のauthroizeエラーをLaravelテストでアサートする方法を説明します。
mount()メソッド内のauthorize
例えば以下のような感じのLivewireのmount()内のauhtorize。
public function mount($postId)
{
$post = Post::find($postId);
// このauthorizeエラーをアサートしたい
$this->authorize("update", post);
...
}
通常の方法だとアサートできない
これ、普通にLivewire3のドキュメントにある通りに、
<?php
namespace Tests\Feature\Livewire;
use App\Livewire\UpdatePost;
use Livewire\Livewire;
use App\Models\User;
use App\Models\Post;
use Tests\TestCase;
class UpdatePostTest extends TestCase
{
/** @test */
public function cant_update_another_users_post()
{
$user = User::factory()->create();
$stranger = User::factory()->create();
$post = Post::factory()->for($stranger)->create();
Livewire::actingAs($user)
->test(UpdatePost::class, ['post' => $post])
->set('title', 'Living the lavender life')
->call('save')
->assertUnauthorized();
}
}
のようにやってもアサートできず、
ErrorException
Trying to access array offset on value of type null
at vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:88
84▕ }
85▕
86▕ public function update($snapshot, $updates, $calls)
87▕ {
➜ 88▕ $data = $snapshot['data'];
89▕ $memo = $snapshot['memo'];
90▕
91▕ if (config('app.debug')) $start = microtime(true);
92▕ [ $component, $context ] = $this->fromSnapshot($snapshot);
のようなエラーになる。
何が起きているんだろう
結論から言えば、私の力ではわからない。
でも一応色々やって考察したものを残す。
恐らく、以下のような流れでエラーになっている。
- mount()で
AuthorizationException
が発生 - mount()でエラーが発生することでLivewireがしっかり初期化されない
- Laravelの例外ハンドラがエラーを受け取りHTTPレスポンスに変換する
- Livewireのsnapshotが作成されないままHTTPレスポンスが作成される
- snapshotが無いので表示する過程でエラー
という感じだと思う。
そもそもLaravelが例外ハンドラというもので勝手にHTTPレスポンスに変換してくれていたことさえ知らなかったよ私は。
じゃあどうすればいいか
公式ドキュメントにあったassertUnauthorized()
は
/**
* Assert that the response has a 401 "Unauthorized" status code.
*
* @return $this
*/
public function assertUnauthorized()
{
return $this->assertStatus(401);
}
のように401レスポンスを期待するもの。
恐らく本来はLaravelの例外ハンドラがAuthorizationExceptionを401のレスポンスに変換してくれる。
しかし、今回はレスポンス変換に失敗してしまうので以下の2つを使う。
// 例外ハンドラを無効化
$this->withoutExceptionHandling();
// AuthorizationExceptionを直接アサート
$this->expectException(AuthorizationException::class);
これ等を使うことで、AuthorizationExceptionを直接拾ってアサートできるという感じ。
結論
以下のように
を付けるとアサート可能。
/** @test */
public function cant_update_another_users_post()
{
$this->withoutExceptionHandling();
$this->expectException(AuthorizationException::class);
$user = User::factory()->create();
$stranger = User::factory()->create();
$post = Post::factory()->for($stranger)->create();
Livewire::actingAs($user)
->test(UpdatePost::class, ['post' => $post])
->set('title', 'Living the lavender life')
->call('save');
}
しかし、テスト環境と本番環境で同じ処理になるとすれば、mount()でのauthorizeは適切にレスポンスできないということになりそう。
どちらにしろエラーなので、妥協はできなくはない気もするけど、どうしようかね。