【Laravel】テストエラー時の対応

Laravel

この記事のゴール

  • テストエラーが発生したときの対応の仕方がわかる

前提条件

  • Laravelでの自動テストコードを書いて実行したことがある

 ※Laravelでの自動テストについては前回の記事をどうぞ。

これからやること

  • テストコードの準備
  • テスト実行
  • エラーの確認と原因の特定
  • コードの修正
  • テストの再実施
  • コードの再修正
  • テストの再々実施

テストコードの準備

<?php

namespace Tests\Feature;

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

class TodoTest extends TestCase
{
    public function testExample(): void
    {

    }

    public function test_index_can_get_todo_list(): void
    {
        Todo::truncate();
        $response = $this->get(route('todo'))
                         ->assertSee('TUDUリスト')
                         ->assertSee('新規登録')
                         ->assertOk();
        foreach ($todos as $todo) {
            $response->assertSee($todo->todo);
        }
    }

    public function test_retrieve_can_be_rendered(): void
    {
        Todo::truncate();
        $todos = Todo::factory()->count(5)->create();
        foreach ($todos as $todo) {
            $this->get(route('todo.detail'))
                 ->assertSee('TODO詳細')
                 ->assertSee('[編集]')
                 ->assertSee('[削除]')
                 ->assertSee('todo一覧')
                 ->assertSee('やること: ' . $todo->todo)
                 ->assertSee('状態:' . (1 == $todo->done ? '官僚' : ''))
                 ->assertOk();
        }
    }

    public function test_store_can_store(): void
    {
        $todo = Todo::factory()->definition();
        $todo['done'] = null;
        $todo['id'] = 6;
        $this->post(route('todo.store'), $todo)
             ->assertValid()
             ->assertRedirect(route('todo.index'))
             ->assertSee($todo['todo']);
    }
}

テスト実行

では、このコードを実行してみます。

php artisan test

イイ感じで始まりました。

あれっ?「3 failed, 1 riskey, 24 passed (57 assertions)」って表示されました。

失敗3、危険1、合格24ですね。

エラーの確認と原因の特定

こういうときには、表示をスクロールして遡り、最初のエラーや警告から見ていきます。

クラス名「Tests\Feature\TodoTest」の左側に「FAILED」と表示され、その下に

各テストメソッドの結果が表示され、失敗した理由とコードのエラー発生箇所が表示されます。

では、最初の「example」メソッドの黄色い警告ですが、

「This test did not perform any assertions」とあります。

「このテストは何の検証も実施してないじゃないか」ということです。

    public function testExample(): void
    {

    }

中身が空のメソッドですね。最低1つはアサーションを記述しないと叱られます。

次に、「index can get todo list」のエラーで「Route[todo] not defined.」とあります。

「Route情報の[todo]なんて定義されてないよ!」ということです。

        $response = $this->get(route('todo'))

あ、ここはTODO一覧ページのURLなので、

route(‘todo.index’) にしないといけませんね。

次の内容を確認していきます。

「retrieve can be rendered」の箇所で、次のメッセージが出ています。

Missing required parameter for [Route: todo.detail] [URI: todo/{id}] [Missing parameter:id].

Route: todo.detail で要求される引数が見当たりません。

[URIの定義は: todo/{id}] です。

[引数の id が見当たりません。] ということです。

            $this->get(route('todo.detail'))

あ、route() の引数で id 渡すの忘れてましたね。

次を見ていきます。

「store can store」の箇所で、「Response has unexpected validation errors:」

とあります。バリデーションエラーが次のように表示されています。

{
    "done": [
        "The done field is required."
    ]
}

項目 done は必須だよと言っています。テストコードを見てみます。

        $todo['done'] = null;

あ、何やってるんですかね。

ここで確認できる警告とエラーは以上です。原因も全て特定しました。

全てテストコード側のミスでしたね。

コードの修正1:testExample()

では、テストコードを修正していきます。

testExample() の警告「This test did not perform any assertions」の対応です。

    public function testExample(): void
    {

    }

これは不要なメソッドでした。メソッドを丸ごと削除します。

コードの修正2:test_index_can_get_todo_list()

「Route[todo] not defined.」の対応をしていきます。

        $response = $this->get(route('todo'))

route(‘todo’) を route(‘todo.index’) に修正します。

        $response = $this->get(route('todo.index'))

一応、正しいルーティングになっているかどうかを、

[routes/web.php] で確認すると確実です。

コードの修正3:test_retrieve_can_be_rendered()

Missing required parameter for [Route: todo.detail] [URI: todo/{id}] [Missing parameter: id].

この対応をしていきます。

            $this->get(route('todo.detail'))

route() の第二引数に id を指定する必要があります。

            $this->get(route('todo.detail', ['id' => $todo->id]))

確かに id を指定しました。

コードの修正4:test_store_can_store()

「Response has unexpected validation errors:」の対応です。

項目 done の必須チェックでエラーでしたね。

        $todo['done'] = null;

この行は不要なので削除です。

これで修正は完了です。

<?php

namespace Tests\Feature;

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

class TodoTest extends TestCase
{
    public function test_index_can_get_todo_list(): void
    {
        Todo::truncate();
        $response = $this->get(route('todo.index'))
                         ->assertSee('TUDUリスト')
                         ->assertSee('新規登録')
                         ->assertOk();
        foreach ($todos as $todo) {
            $response->assertSee($todo->todo);
        }
    }

    public function test_retrieve_can_be_rendered(): void
    {
        Todo::truncate();
        $todos = Todo::factory()->count(5)->create();
        foreach ($todos as $todo) {
            $this->get(route('todo.detail', ['id' => $todo->id]))
                 ->assertSee('TODO詳細')
                 ->assertSee('[編集]')
                 ->assertSee('[削除]')
                 ->assertSee('todo一覧')
                 ->assertSee('やること: ' . $todo->todo)
                 ->assertSee('状態:' . (1 == $todo->done ? '官僚' : ''))
                 ->assertOk();
        }
    }

    public function test_store_can_store(): void
    {
        $todo = Todo::factory()->definition();
        $todo['id'] = 6;
        $this->post(route('todo.store'), $todo)
             ->assertValid()
             ->assertRedirect(route('todo.index'))
             ->assertSee($todo['todo']);
    }
}

テスト再実行

修正完了したのでテストを再実行してみましょう。

手早く確認したいので、TodoTest.php だけテストしてみましょう。

php artisan test tests/Feature/TodoTest.php

この場合は、最後の引数に、artisan からの相対パスでファイル指定します。

あれ、またエラーです。

「To contain: TUDUリスト」って、正しくは「TODOリスト」ですよね。

このように、一つ修正して通っても、その次でエラーになることがあります。

こうして、一つ一つ確実にエラーを潰していきます。

エラーを潰す度にコードの品質が上がると思えば楽しいものです。と思ってください。

次の確認にいきましょう。

「To contain: 状態:官僚」って、「状態:完了」ですよね。

次の確認です。

これ、ちょっとよくわからないですよね。

バリデーション通っているんだから、一覧に表示されるはずでしょ?

こういうときには、レスポンスのHTMLを出力してみましょう。

    public function test_store_can_store(): void
    {
        $todo = Todo::factory()->definition();
        $todo['id'] = 6;
        $this->post(route('todo.store'), $todo)
             ->assertValid()
             ->assertRedirect(route('todo.index'))
             ->dump()
             ->assertSee($todo['todo']);
    }

assertRedirect() の下に dump() を入れてみました。この状態でテスト実行してみます。

POST後のHTTPレスポンスのHTMLが表示されました。

リダイレクトの指定が書かれているだけでした。

ブラウザがHTTPレスポンスのステータスコード301または302を受け取り

HTTPレスポンスヘッダに含まれるリダイレクト先のURLを見てから、

ブラウザが自らリダイレクト先のURLへアクセスするというのが

リダイレクトの仕組みです。

なので、リダイレクト先のコンテンツを取得するには、

新たにアクセスしなければなりません。

コードの再修正:test_index_can_get_todo_list()

原因の特定をしたので、再びコードを修正していきましょう。

                         ->assertSee('TUDUリスト')

TUDU → TODO に修正します。

                         ->assertSee('TODOリスト')

コードの再修正:test_test_retrieve_can_be_rendered()

                 ->assertSee('状態:' . (1 == $todo->done ? '官僚' : ''))

官僚 → 完了 に修正します。

                 ->assertSee('状態:' . (1 == $todo->done ? '完了' : ''))

コードの再修正:test_store_an_store()

    public function test_store_can_store(): void
    {
        $todo = Todo::factory()->definition();
        $todo['id'] = 6;
        $this->post(route('todo.store'), $todo)
             ->assertValid()
             ->assertRedirect(route('todo.index'));
             ->assertSee($todo['todo']);
    }

$todo[‘id’] = 6; とかも要らないですよね。削ります。

あと、リダイレクト後にTODOリストページに改めてアクセスします。

    public function test_store_can_store(): void
    {
        $todo = Todo::factory()->definition();
        $this->post(route('todo.store'), $todo)
             ->assertValid()
             ->assertRedirect(route('todo.index'));
        $this->get(route('todo.index'))
             ->assertSee($todo['todo']);
    }

よし、全部直した!

テスト再々実行

今度こそ通りますように。そう、システム開発では神様にお願いしたくなるので、

開発神社というものがあるのですよ。

あれれ、また何かでました。今度はテンプレート側ですかね。

    <body>
        <h1>{{$pageTitle}}</h1>
        <a href="{{route('todo.create')}}">新 規 登 録</a><br />
        @isset($todos)
        @foreach($todos as $todo)

あらら、余計なスペースが入って「新 規 登 録」になってますね。

修正してテスト再々々実行です。

うわー、またエラー。「Undefined variable $todos」→「未定義の変数 $todos」。

    public function test_index_can_get_todo_list(): void
    {
        Todo::truncate();
        $response = $this->get(route('todo.index'))
                         ->assertSee('TODOリスト')
                         ->assertSee('新規登録')
                         ->assertOk();
        foreach ($todos as $todo) {
            $response->assertSee($todo->todo);
        }
    }

あ、「Todo::truncate();」で空にしたまま、レコード作成していなかったのと、

作成したレコードを $todo に格納する箇所がないですね。修正します。

    public function test_index_can_get_todo_list(): void
    {
        Todo::truncate();
        $todos = Todo::factory()->count(5)->create();
        $response = $this->get(route('todo.index'))
                         ->assertSee('TODOリスト')
                         ->assertSee('新規登録')
                         ->assertOk();
        foreach ($todos as $todo) {
            $response->assertSee($todo->todo);
        }
    }

テストを再々々々実行します。

今度こそオールグリーン!でもこれで安心してはいけません。

念のため、全てのテストを実行しましょう。

php artisan test

これが本当のオールグリーン!これで安心して眠れますね。

おやすみなさい。

コメント

タイトルとURLをコピーしました