見出し画像

LaravelでCRUDシステムを構築する②

 前回の記事で作成したメンバー管理システムのテストコードを作成します。

PHPUnitの基本に関しては下記の記事を参考にしてください。

筆者の開発環境

PC:Apple M1 チップ搭載MacBook Air
OS:macOS Sonoma 14.1(23B74)
MAMP:6.8
PHP:8.2.0
Laravel:10.29.0

大見出し

 LaravelにはPHPUnitがはじめから含まれており、すでに最初のテストを実行する準備が整っています。「tests」フォルダ内に「Feature」フォルダと「Unit」フォルダが準備されており、それぞれにテストのサンプルが置かれています。下記のコマンドを実行し、テストが通ることを確認してください。

php artisan test

下記のように表示されれば成功です。

   PASS  Tests\Unit\ExampleTestthat true is true                                                                                                               0.01s  

   PASS  Tests\Feature\ExampleTestthe application returns a successful response                                                                                   0.13s  

  Tests:    2 passed (2 assertions)
  Duration: 0.21s

一覧画面のテスト

下記のコマンドを実行し一覧画面用のテストファイルを作成してください。

php artisan make:test MemberIndexTest

 tests/Featureフォルダ内にMemberIndexTest.phpが作成されれば成功です。元からあるテストを消して下記のように編集してください。/memberにGETアクセスしてステータスコード200が返ってくればテスト成功です。

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class MemberIndexTest extends TestCase
{
    /**
     * @test
     */
    public function ステータス200が返ること(): void
    {
        $response = $this->get(route('member.index'));

        $response->assertOk();
    }
}

編集できたら下記のコマンドを実行してください。

php artisan test

下記のように表示されたら成功です。

   PASS  Tests\Unit\ExampleTestthat true is true

   PASS  Tests\Feature\ExampleTestthe application returns a successful response                                                                               0.12s  

   PASS  Tests\Feature\MemberTestexample                                                                                                                     0.02s  
  ✓ ステータス200が返ること                                                                                                               0.04s  

  Tests:    4 passed (4 assertions)
  Duration: 0.25s

 続いて、リストが指定通りの順番で表示されているかテストします。
MemberIndexTest.phpを下記のように編集してください。RefreshDatabaseトレイトをuseすることを忘れないでください。このトレイトはテスト毎にデータベースをクリーンにしてくれる働きをします。

<?php

namespace Tests\Feature;

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

class MemberIndexTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @test
     */
    public function ステータス200が返ること(): void
    {
        $response = $this->get(route('member.index'));

        $response->assertOk();
    }

    /**
     * @test
     */
    public function メンバー一覧が表示されること(): void
    {
        Member::create(['name' => '山田太郎', 'email' => 'yamada@example.com']);
        Member::create(['name' => '田中一郎', 'email' => 'tanaka@example.com']);
        Member::create(['name' => '鈴木次郎', 'email' => 'suzuki@example.com']);

        $this->get(route('member.index'))
            ->assertSeeInOrder([
                '鈴木次郎',
                '田中一郎',
                '山田太郎',
            ])
            ->assertSeeInOrder([
                'suzuki@example.com',
                'tanaka@example.com',
                'yamada@example.com',
            ]);
    }
}

編集できたら下記のコマンドを実行してください。

php artisan test

下記のように表示されたら成功です。

  PASS  Tests\Unit\ExampleTestthat true is true

   PASS  Tests\Feature\ExampleTestthe application returns a successful response                                                                               0.12s  

   PASS  Tests\Feature\MemberIndexTest
  ✓ ステータス200が返ること                                                                                                               0.25s  
  ✓ メンバー一覧が表示されること                                                                                                              0.03s  

  Tests:    4 passed (5 assertions)
  Duration: 0.48s

新規登録、編集、削除の各種ボタンが表示されていることも確認しましょう。MemberIndexTest.phpを下記のように編集してください。

<?php

(省略)

class MemberIndexTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function メンバー一覧が表示されること(): void
    {
        (省略)
    }

    /**
     * @test
     */
    public function 新規登録ボタンが表示されること(): void
    {
        $this->get(route('member.index'))->assertSeeText('新規登録');
    }

    /**
     * @test
     */
    public function 編集ボタンが表示されること(): void
    {
        $this->get(route('member.index'))->assertSeeText('編集');
    }

    /**
     * @test
     */
    public function 削除ボタンが表示されること(): void
    {
        $this->get(route('member.index'))->assertSeeText('削除');
    }
}

編集できたら下記のコマンドを実行してください。

php artisan test

下記のように表示されれば成功です。

   PASS  Tests\Unit\ExampleTestthat true is true

   PASS  Tests\Feature\ExampleTestthe application returns a successful response                                                                               0.12s  

   PASS  Tests\Feature\MemberIndexTest
  ✓ ステータス200が返ること                                                                                                               0.24s  
  ✓ メンバー一覧が表示されること                                                                                                              0.03s  
  ✓ 新規登録ボタンが表示されること                                                                                                             0.02s  
  ✓ 編集ボタンが表示されること                                                                                                               0.02s  
  ✓ 削除ボタンが表示されること                                                                                                               0.02s  

  Tests:    7 passed (8 assertions)
  Duration: 0.53s

詳細画面のテスト

下記のコマンドを実行し詳細画面用のテストファイルを作成してください。

php artisan make:test MemberShowTest

 tests/Featureフォルダ内にMemberShowTest.phpが作成されれば成功です。元からあるテストを消して下記のように編集してください。

<?php

namespace Tests\Feature;

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

class MemberShowTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @test
     */
    public function ステータス200が返ること(): void
    {
        $member = Member::create(['name' => '山田太郎', 'email' => 'yamada@example.com']);

        $this->get(route('member.show', $this->member->id))->assertOk();
    }
}

 編集できたら下記のコマンドを実行してください。テストファイル名を指定し特定のテストファイルのみテストを実行するようにします。

php artisan test tests/Feature/MemberShowTest.php

下記のように表示されれば成功です。(以後はテスト結果の表示の解説は省略します。)

   PASS  Tests\Feature\MemberShowTest
  ✓ ステータス200が返ること                                                                                                               0.35s  

  Tests:    1 passed (1 assertions)
  Duration: 0.39s

 続いて、メンバーの詳細情報が表示されていることを確認します。MemberShowTest.phpを下記のように編集してください。memberインスタンスを作る処理をsetup()メソッドにまとめました。

<?php

namespace Tests\Feature;

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

class MemberShowTest extends TestCase
{
    use RefreshDatabase;

    private $member;

    public function setUp(): void
    {
        parent::setUp();

        $this->member = Member::create([
            'name' => '山田太郎',
            'email' => 'yamada@example.com',
        ]);
    }

    /**
     * @test
     */
    public function ステータス200が返ること(): void
    {
        $this->get(route('member.show', $this->member->id))->assertOk();
    }

    /**
     * @test
     */
    public function メンバーの詳細情報が表示されること()
    {
        $this->get(route('member.show', $this->member->id))
            ->assertSeeInOrder([
                'ID: ' . $this->member->id,
                '名前: 山田太郎',
                'メールアドレス: yamada@example.com',
                '作成日時: ' . $this->member->created_at,
                '更新日時: ' . $this->member->updated_at,
            ]);
    }
}

 編集できたら下記のコマンドを実行してテストが通ることを確認してください。

php artisan test tests/Feature/MemberShowTest.php

「戻る」ボタンが表示されていることを確認します。

<?php

(省略)

class MemberShowTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function メンバーの詳細情報が表示されること()
    {
        $this->get(route('member.show', $this->member->id))
            ->assertSeeInOrder([
                'ID: ' . $this->member->id,
                '名前: 山田太郎',
                'メールアドレス: yamada@example.com',
                '作成日時: ' . $this->member->created_at,
                '更新日時: ' . $this->member->updated_at,
            ]);
    }

    /**
     * @test
     */
    public function 戻るボタンが表示されること(): void
    {
        $this->get(route('member.show', $this->member->id))
            ->assertSeeText('戻る');
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberShowTest.php

新規登録処理のテスト

 下記のコマンドを実行し新規登録処理用のテストファイルを作成してください。

php artisan make:test MemberCreateTest

 登録フォーム画面が表示されることを確認します。MemberCreateTest.phpを下記のように編集してください。

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class MemberCreateTest extends TestCase
{
    /**
     * @test
     */
    public function 登録フォームが表示されること(): void
    {
        $this->get(route('member.create'))->assertOk();
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

 登録に成功した場合、一覧画面にリダイレクトされることを確認します。MemberCreateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberCreateTest extends TestCase
{
    /**
     * @test
     */
    public function 登録フォームが表示されること(): void
    {
        $this->get(route('member.create'))->assertOk();
    }

    /**
     * @test
     */
    public function 一覧画面にリダイレクトされること(): void
    {
        $params = ['name' => '山田太郎', 'email' => 'yamada@example.com'];

        $this->post(route('member.store'), $params)
            ->assertRedirect(route('member.index'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

 POSTされたデータでMemberがデータベースに登録されたことを確認します。MemberCreateTest.phpを下記のように編集してください。POSTするパラメーターはデータプロバイダーから提供するように修正します。
 assertDatabaseHas()メソッドは第1引数に指定されたテーブル内に、第2引数で指定された配列と一致するレコードがあるかを検証します。

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class MemberCreateTest extends TestCase
{
    use RefreshDatabase;

    public static function dataProvider(): array
    {
        return [
            ['山田太郎', 'yamada@example.com'],
        ];
    }

    /**
     * @test
     */
    public function 登録フォームが表示されること(): void
    {
        $this->get(route('member.create'))->assertOk();
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 一覧画面にリダイレクトされること($name, $email): void
    {
        $this->post(route('member.store'), compact('name', 'email'))
            ->assertRedirect(route('member.index'));
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function データベースに登録されること($name, $email): void
    {
        $this->post(route('member.store'), compact('name', 'email'));
        $this->assertDatabaseHas('members', compact('name', 'email'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

 一覧画面にフラッシュメッセージが表示されることを確認します。MemberCreateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberCreateTest extends TestCase
{
    (省略)

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function データベースに登録されること($name, $email): void
    {
        $this->post(route('member.store'), compact('name', 'email'));
        $this->assertDatabaseHas('members', compact('name', 'email'));
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 登録に成功した場合、一覧画面にフラッシュメッセージが表示されること($name, $email): void
    {
        $this->post(route('member.store'), compact('name', 'email'));
        $this->get(route('member.index'))->assertSeeText('メンバーの登録に成功しました。');
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

 続いて、登録に失敗した時の動作を検証します。バリデーションエラーが発生した場合、登録フォームにリダイレクトされることを確認します。MemberCreateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberCreateTest extends TestCase
{
    (省略)

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 登録に成功した場合、一覧画面にフラッシュメッセージが表示されること($name, $email): void
    {
        $this->post(route('member.store'), compact('name', 'email'));
        $this->get(route('member.index'))->assertSeeText('メンバーの登録に成功しました。');
    }

    /**
     * @test
     */
    public function バリデーションエラーの場合、登録フォームへリダイレクトされること(): void
    {
        $this->from(route('member.create'))
            ->post(route('member.store'), ['name' => '', 'email' => ''])
            ->assertRedirect(route('member.create'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

 バリデーションのテストを行います。MemberCreateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberCreateTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function バリデーションエラーの場合、登録フォームへリダイレクトされること(): void
    {
        $this->from(route('member.create'))
            ->post(route('member.store'), ['name' => '', 'email' => ''])
            ->assertRedirect(route('member.create'));
    }

    /**
     * @test
     */
    public function nameが空の場合バリデーションエラーが発生すること(): void
    {
        $this->post(route('member.store'), ['name' => '', 'email' => 'yamada@example.com'])
            ->assertInvalid(['name' => '名前を入力してください。']);
    }

    /**
     * @test
     */
    public function emailが空の場合バリデーションエラーが発生すること(): void
    {
        $this->post(route('member.store'), ['name' => '山田太郎', 'email' => ''])
            ->assertInvalid(['email' => 'メールアドレスを入力してください。']);
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberCreateTest.php

更新処理のテスト

下記のコマンドを実行し更新処理用のテストファイルを作成してください。

php artisan make:test MemberUpdateTest

 更新フォーム画面が表示されることを確認します。MemberUpdateTest.phpを下記のように編集してください。

<?php

namespace Tests\Feature;

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

class MemberUpdateTest extends TestCase
{
    use RefreshDatabase;

    private $member;

    public function setUp(): void
    {
        parent::setUp();

        $this->member = Member::create([
            'name' => '山田太郎',
            'email' => 'yamada@example.com',
        ]);
    }

    /**
     * @test
     */
    public function 更新フォームが表示されること(): void
    {
        $this->get(route('member.edit', $this->member))
            ->assertOk();
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

 更新に成功した場合、一覧画面にリダイレクトされることを確認します。MemberUpdateTest.phpを下記のように編集してください。更新用のパラメーターはデータプロバイダーから渡しています。

<?php

(省略)

class MemberUpdateTest extends TestCase
{
    use RefreshDatabase;

    private $member;

    public function setUp(): void
    {
        parent::setUp();

        $this->member = Member::create([
            'name' => '山田太郎',
            'email' => 'yamada@example.com',
        ]);
    }

    public static function dataProvider(): array
    {
        return [
            ['山田三郎', 'yamadasaburou@example.com'],
        ];
    }

    /**
     * @test
     */
    public function 更新フォームが表示されること(): void
    {
        $this->get(route('member.edit', $this->member))
            ->assertOk();
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 更新に成功した場合、一覧画面にリダイレクトされること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'))
            ->assertRedirect(route('member.index'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

 メンバーがパラメーター通りに更新されることを確認します。MemberUpdateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberUpdateTest extends TestCase
{
    (省略)

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 更新に成功した場合、一覧画面にリダイレクトされること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'))
            ->assertRedirect(route('member.index'));
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function パラメーター通りに更新されること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'));
        $this->assertDatabaseHas('members', compact('name', 'email'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

 一覧画面にフラッシュメッセージが表示されることを確認します。MemberUpdateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberUpdateTest extends TestCase
{
    (省略)

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function パラメーター通りに更新されること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'));
        $this->assertDatabaseHas('members', compact('name', 'email'));
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 更新に成功した場合、一覧画面にフラッシュメッセージが表示されること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'));
        $this->get(route('member.index'))->assertSeeText('メンバーの更新に成功しました。');
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

 続いて、更新に失敗した時の動作を検証します。バリデーションエラーが発生した場合、更新フォームにリダイレクトされることを確認します。MemberCreateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberUpdateTest extends TestCase
{
    (省略)

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function 更新に成功した場合、一覧画面にフラッシュメッセージが表示されること($name, $email): void
    {
        $this->patch(route('member.update', $this->member), compact('name', 'email'));
        $this->get(route('member.index'))->assertSeeText('メンバーの更新に成功しました。');
    }

    /**
     * @test
     */
    public function バリデーションエラーの場合、更新フォームへリダイレクトされること()
    {
        $this->from(route('member.edit', $this->member))
            ->patch(route('member.update', $this->member), ['name' => '', 'email' => ''])
            ->assertRedirect(route('member.edit', $this->member));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

 バリデーションのテストを行います。MemberUpdateTest.phpを下記のように編集してください。

<?php

(省略)

class MemberUpdateTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function バリデーションエラーの場合、更新フォームへリダイレクトされること()
    {
        $this->from(route('member.edit', $this->member))
            ->patch(route('member.update', $this->member), ['name' => '', 'email' => ''])
            ->assertRedirect(route('member.edit', $this->member));
    }

    /**
     * @test
     */
    public function nameが空の場合バリデーションエラーが発生すること(): void
    {
        $this->patch(route('member.update', $this->member), ['name' => '', 'email' => 'yamadasaburou@example.com'])
            ->assertInvalid(['name' => '名前を入力してください。']);
    }

    /**
     * @test
     */
    public function emailが空の場合バリデーションエラーが発生すること(): void
    {
        $this->patch(route('member.update', $this->member), ['name' => '山田三郎', 'email' => ''])
            ->assertInvalid(['email' => 'メールアドレスを入力してください。']);
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberUpdateTest.php

削除処理のテスト

下記のコマンドを実行し削除処理用のテストファイルを作成してください。

php artisan make:test MemberDestroyTest

MemberDestroyTest.phpを下記のように編集してください。

<?php

namespace Tests\Feature;

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

class MemberDestroyTest extends TestCase
{
    use RefreshDatabase;

    private $member;

    public function setUp(): void
    {
        parent::setUp();

        $this->member = Member::create([
            'name' => '山田太郎',
            'email' => 'yamada@example.com',
        ]);
    }

    /**
     * @test
     */
    public function データベースから削除されること(): void
    {
        $this->delete(route('member.destroy', $this->member));
        $this->assertDatabaseMissing('members', ['name' => '山田太郎', 'email' => 'yamada@example.com']);
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberDestroyTest.php

 削除に成功すると一覧画面へリダイレクトされることを確認します。MemberDestroyTest.phpを下記のように編集してください。

<?php

(省略)

class MemberDestroyTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function データベースから削除されること(): void
    {
        $this->delete(route('member.destroy', $this->member));
        $this->assertDatabaseMissing('members', ['name' => '山田太郎', 'email' => 'yamada@example.com']);
    }

    /**
     * @test
     */
    public function 削除に成功した場合、一覧画面へリダイレクトされること(): void
    {
        $this->delete(route('member.destroy', $this->member))
            ->assertRedirect(route('member.index'));
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberDestroyTest.php

 一覧画面にフラッシュメッセージが表示されることを確認します。MemberDestroyTest.phpを下記のように編集してください。

<?php

(省略)

class MemberDestroyTest extends TestCase
{
    (省略)

    /**
     * @test
     */
    public function 削除に成功した場合、一覧画面へリダイレクトされること(): void
    {
        $this->delete(route('member.destroy', $this->member))
            ->assertRedirect(route('member.index'));
    }

    /**
     * @test
     */
    public function 削除に成功した場合、一覧画面にフラッシュメッセージが表示されること(): void
    {
        $this->delete(route('member.destroy', $this->member));
        $this->get(route('member.index'))->assertSeeText('メンバーの削除に成功しました。');
    }
}

 編集できたら下記のコマンドを実行しテストが通ることを確認してください。

php artisan test tests/Feature/MemberDestroyTest.php

解説は以上です。おつかれさまでした。

PHP/Laravelのシステム開発は株式会社パパグラムへぜひご相談ください。

この記事が気に入ったらサポートをしてみませんか?