はじめに
テストを書いている際、バリデーションエラーを返すスクリプトを書いているはずなのに、なぜかテストをパスしてしまうことがありました。
その理由は何でしょうか?
有識者の方は、症例を見ながら是非推理していただければと思います。
環境
Laravel 9.52.16
問題
- 以下のようなコントローラのアクションをテストしたい
public function store(Request $request)
{
// バリデーション
$request->validate([
'title' => 'required|max:2000',
'sales_abstract' => 'required|max:2000',
'category' => 'required|max:50',
'budget' => 'required|numeric',
'schedule' => 'required|date',
]);
// 認証済みユーザの発注として作成(リクエストされた値を元に作成)
$request->user()->sales_orders()->create([
'title' => $request->title,
'sales_abstract' => $request->sales_abstract,
'category' => $request->category,
'budget' => $request->budget,
'schedule' => $request->schedule,
]);
// 前のURLへリダイレクトさせる
return back();
}
そこで最初に次のようなテストコード(失敗例)を書きました。
public function test_sales_store(): void
{
// テスト用のユーザを作成
$user = User::factory()->create();
// 作成したユーザで認証
$response = $this->actingAs($user)->post('/my/sales/store');
$response->assertStatus(302);
}
しかし、上記テスト内ではpostメソッドにフォームデータを渡していません。
先ほどのコントローラ内にはバリデーションがあるので、フォームデータがなければバリデーションエラーを返すはずです。
つまり自分はこのテストが通らず、エラーを返すことを期待していました。
root:/var/www/html/niches# php artisan test --filter test_sales_store
Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0
Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0
PASS Tests\Feature\SalesOrdersControllerTest
✓ sales store
Tests: 1 passed
Time: 9.81s
しかしなぜか上のようにテストが通ってしまい「???」となっていました。
バリデーションエラーは発生しなかったということでしょうか?
解決法(答え)
- 実際にはバリデーションエラーは発生していた
- しかしバリデーションエラーもreturn back()と同じく、302リダイレクトを返す
- そのためassertStatus(302)が通った
- よってテストが通った
バリデーションエラー時の動作
①リダイレクト:
バリデーションルールに違反すると、Laravelはユーザをフォームがあった元のページにリダイレクトします。ユーザがフォームを修正して再度送信できるようにするためです。
②エラーメッセージの表示:
そしてリダイレクトともに、「パスワードは半角英数字16字以内で入力してください」といったエラーメッセージが表示されます。
③ステータスコード:
そしてこのリダイレクトはHttpステータスコード「302」を伴います。
つまり自分が書いたテストコードは、成功しても失敗してもパスされるような構造になってしまっていました。
この場合の正しいテストコード
public function test_sales_store(): void
{
$user = User::factory()->create();
$formData = [
'title' => 'テストタイトル',
'sales_abstract' => 'テスト概要',
'category' => 'テストカテゴリー',
'budget' => 1000,
'schedule' => '2023-01-01',
];
$response = $this->actingAs($user)->post('/my/sales/store', $formData);
$response->assertStatus(302);
$this->assertDatabaseHas('sales_orders', ['title' => 'テストタイトル']);
}
まずはフォームデータを用意し、postメソッドに引数として渡します。
そして最後に「$this->assertDatabaseHas('sales_orders', ['title' => 'テストタイトル']);」と書いてあげることで、
正しくフォームデータがデータベースに保存されているか、テストすることができます。
まとめ
よく考えてみたら、自分も日常生活の中でパスワードを設定する時にバリデーションエラーを起こす時があります。
そしてその時には、必ずパスワードを設定する画面に戻されていました。
今回の経験は「内部的にはこうなってるんや」と考える良い時間になりました。