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\ExampleTest
✓ that true is true 0.01s
PASS Tests\Feature\ExampleTest
✓ the 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\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the application returns a successful response 0.12s
PASS Tests\Feature\MemberTest
✓ example 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\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the 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\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the 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のシステム開発は株式会社パパグラムへぜひご相談ください。