実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その47)
0. 初めに
Webアプリケーション開発についてゼロから解説しているシリーズで、今回でテスト編の第5回目です!
引き続きコントローラーのFeatureテストを行っていきます!
1. 大学コントローラー
まず、大学コントローラーについてのテストケースを作成します。
1.1 作成
実行コマンド
$ php artisan make:test Controllers/UniversityControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\University;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UniversityControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// index
// -------------------------
public function test_大学一覧を表示できる(): void
{
// Arrange
University::factory()->count(3)->create();
// Act
$response = $this->get(route('universities.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('University/Index')
->has('universities.data', 3)
);
}
public function test_キーワードで大学を検索できる(): void
{
// Arrange
University::factory()->create(['name' => '東京大学']);
University::factory()->create(['name' => '大阪大学']);
// Act
$response = $this->get(route('universities.index', ['query' => '東京']));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('University/Index')
->has('universities.data', 1)
->where('query', '東京')
);
}
// -------------------------
// store
// -------------------------
public function test_認証済みユーザーが大学を作成できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)
->post(route('universities.store'), [
'name' => 'テスト大学',
'type' => 'national',
]);
// Assert
$response->assertRedirect();
$this->assertDatabaseHas('universities', [
'name' => 'テスト大学',
'type' => 'national',
'created_by' => $user->id,
]);
}
public function test_未認証ユーザーは大学を作成できない(): void
{
// Act
$response = $this->post(route('universities.store'), [
'name' => 'テスト大学',
'type' => 'national',
]);
// Assert
$response->assertRedirect('/');
}
public function test_バリデーションエラーで大学を作成できない(): void
{
// Arrange
$user = User::factory()->create();
// Act(name が空)
$response = $this->actingAs($user)
->post(route('universities.store'), [
'name' => '',
'type' => 'national',
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseMissing('universities', ['type' => 'national']);
}
public function test_重複した大学名では作成できない(): void
{
// Arrange
$user = User::factory()->create();
University::factory()->create(['name' => 'テスト大学']);
// Act
$response = $this->actingAs($user)
->post(route('universities.store'), [
'name' => 'テスト大学',
'type' => 'national',
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseCount('universities', 1);
}
// -------------------------
// update
// -------------------------
public function test_認証済みユーザーが大学情報を更新できる(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create([
'name' => '更新前大学',
'type' => 'national',
'version' => 1,
]);
// Act
$response = $this->actingAs($user)
->put(route('universities.update', $university), [
'name' => '更新後大学',
'type' => 'public',
'comment' => '名称変更のため更新',
'version' => 1,
]);
// Assert
$response->assertRedirect();
$this->assertDatabaseHas('universities', [
'id' => $university->id,
'name' => '更新後大学',
'type' => 'public',
'version' => 2,
]);
}
public function test_バージョン不一致の場合は更新できない(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create([
'name' => '更新前大学',
'version' => 2,
]);
// Act(古いバージョン番号を送信)
$response = $this->actingAs($user)
->put(route('universities.update', $university), [
'name' => '更新後大学',
'type' => 'national',
'comment' => '変更理由',
'version' => 1,
]);
// Assert
$response->assertSessionHasErrors('version');
$this->assertDatabaseHas('universities', [
'id' => $university->id,
'name' => '更新前大学',
]);
}
// -------------------------
// history
// -------------------------
public function test_大学の編集履歴を表示できる(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
$university->users()->attach($user->id, [
'comment' => '初回登録',
'created_at' => now(),
'updated_at' => now(),
]);
// Act
$response = $this->get(route('universities.history', $university));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('University/History')
->has('editHistory', 1)
);
}
}
ポイントは二つです。
一つ目はindexメソッドについてです。
ここでは、assertInertiaというInertia.js限定のアサーションを使うことができます!
通常のLaravelだけですと、リダイレクトのテストしかできませんが、これを用いることで、Reactのどのコンポーネントについてかまでテストすることができます!
historyメソッドに関しても同様です。
二つ目は、updateメソッドです。
これは、同じ大学を複数のユーザーが同時に編集するようなことを防ぐために、実装したバージョン管理の機能(楽観的ロック)をテストしています。
現在のDBに登録されているバージョンと取得済みのバージョンが違う場合は上書きされないようにロックしていたはずです。
1.2 実行
実行コマンド
$ php artisan test --filter=UniversityControllerTest
assertInertiaを使うためにはJavaScriptファイルにアクセスために簡易サーバーを立ち上げていないといけないため、別ターミナルで以下のコマンドをあらかじめ実行しておく必要があります。
実行コマンド
$ npm run dev
2. 学部コントローラー
次は、学部コントローラーです。
大学とほぼ同じです。
実行コマンド
$ php artisan make:test Controllers/FacultyControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\Faculty;
use App\Models\University;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class FacultyControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// index
// -------------------------
public function test_学部一覧を表示できる(): void
{
// Arrange
$university = University::factory()->create();
Faculty::factory()->count(3)->create(['university_id' => $university->id]);
// Act
$response = $this->get(route('faculties.index', $university));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Faculty/Index')
->has('faculties', 3)
->where('university.id', $university->id)
);
}
// -------------------------
// store
// -------------------------
public function test_認証済みユーザーが学部を作成できる(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act
$response = $this->actingAs($user)
->post(route('faculties.store', $university), [
'name' => 'テスト学部',
]);
// Assert
$response->assertRedirect();
$this->assertDatabaseHas('faculties', [
'name' => 'テスト学部',
'university_id' => $university->id,
'created_by' => $user->id,
]);
}
public function test_未認証ユーザーは学部を作成できない(): void
{
// Arrange
$university = University::factory()->create();
// Act
$response = $this->post(route('faculties.store', $university), [
'name' => 'テスト学部',
]);
// Assert
$response->assertRedirect('/');
}
public function test_バリデーションエラーで学部を作成できない(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act(name が空)
$response = $this->actingAs($user)
->post(route('faculties.store', $university), [
'name' => '',
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseMissing('faculties', ['university_id' => $university->id]);
}
public function test_同じ大学内で重複した学部名では作成できない(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
Faculty::factory()->create([
'name' => 'テスト学部',
'university_id' => $university->id,
]);
// Act
$response = $this->actingAs($user)
->post(route('faculties.store', $university), [
'name' => 'テスト学部',
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseCount('faculties', 1);
}
// -------------------------
// update
// -------------------------
public function test_認証済みユーザーが学部情報を更新できる(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create([
'name' => '更新前学部',
'version' => 1,
]);
// Act
$response = $this->actingAs($user)
->put(route('faculties.update', $faculty), [
'name' => '更新後学部',
'comment' => '名称変更のため更新',
'version' => 1,
]);
// Assert
$response->assertRedirect();
$this->assertDatabaseHas('faculties', [
'id' => $faculty->id,
'name' => '更新後学部',
'version' => 2,
]);
}
public function test_バージョン不一致の場合は更新できない(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create([
'name' => '更新前学部',
'version' => 2,
]);
// Act(古いバージョン番号を送信)
$response = $this->actingAs($user)
->put(route('faculties.update', $faculty), [
'name' => '更新後学部',
'comment' => '変更理由',
'version' => 1,
]);
// Assert
$response->assertSessionHasErrors('version');
$this->assertDatabaseHas('faculties', [
'id' => $faculty->id,
'name' => '更新前学部',
]);
}
// -------------------------
// history
// -------------------------
public function test_学部の編集履歴を表示できる(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
$faculty->users()->attach($user->id, [
'comment' => '初回登録',
'created_at' => now(),
'updated_at' => now(),
]);
// Act
$response = $this->get(route('faculties.history', $faculty));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Faculty/History')
->has('editHistory', 1)
);
}
}
実行コマンド
$ php artisan test --filter=FacultyControllerTest
3. 研究室コントローラー
最後は、研究室です。
実行コマンド
$ php artisan make:test Controllers/LabControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\Faculty;
use App\Models\Lab;
use App\Models\Review;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class LabControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// show
// -------------------------
public function test_研究室の詳細を表示できる(): void
{
// Arrange
$lab = Lab::factory()->create();
// Act
$response = $this->get(route('labs.show', $lab));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Lab/Show')
->where('lab.id', $lab->id)
);
}
public function test_研究室の詳細にレビュー一覧が含まれる(): void
{
// Arrange
$lab = Lab::factory()->create();
Review::factory()->count(2)->create(['lab_id' => $lab->id]);
// Act
$response = $this->get(route('labs.show', $lab));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Lab/Show')
->has('lab.reviews', 2)
);
}
// -------------------------
// index
// -------------------------
public function test_研究室一覧を表示できる(): void
{
// Arrange
$faculty = Faculty::factory()->create();
Lab::factory()->count(3)->create(['faculty_id' => $faculty->id]);
// Act
$response = $this->get(route('labs.index', $faculty));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Lab/Index')
->has('labs.data', 3)
->where('faculty.id', $faculty->id)
);
}
public function test_ソート条件を指定して研究室一覧を表示できる(): void
{
// Arrange
$faculty = Faculty::factory()->create();
Lab::factory()->count(2)->create(['faculty_id' => $faculty->id]);
// Act
$response = $this->get(route('labs.index', ['faculty' => $faculty, 'sort' => 'reviews_count']));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Lab/Index')
->where('sort', 'reviews_count')
);
}
// -------------------------
// store
// -------------------------
public function test_認証済みユーザーが研究室を作成できる(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
// Act
$response = $this->actingAs($user)
->post(route('labs.store', $faculty), [
'name' => 'テスト研究室',
'description' => null,
'url' => null,
'professor_name' => null,
'professor_url' => null,
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
]);
// Assert
$response->assertRedirect(route('labs.show', Lab::first()));
$this->assertDatabaseHas('labs', [
'name' => 'テスト研究室',
'faculty_id' => $faculty->id,
'created_by' => $user->id,
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
]);
}
public function test_未認証ユーザーは研究室を作成できない(): void
{
// Arrange
$faculty = Faculty::factory()->create();
// Act
$response = $this->post(route('labs.store', $faculty), [
'name' => 'テスト研究室',
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
]);
// Assert
$response->assertRedirect('/');
}
public function test_バリデーションエラーで研究室を作成できない(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
// Act(name が空)
$response = $this->actingAs($user)
->post(route('labs.store', $faculty), [
'name' => '',
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseMissing('labs', ['faculty_id' => $faculty->id]);
}
public function test_男女比の合計が10を超えると研究室を作成できない(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
// Act(合計 11 を送信)
$response = $this->actingAs($user)
->post(route('labs.store', $faculty), [
'name' => 'テスト研究室',
'gender_ratio_male' => 6,
'gender_ratio_female' => 5,
]);
// Assert
$response->assertSessionHasErrors('gender_ratio_female');
$this->assertDatabaseMissing('labs', ['faculty_id' => $faculty->id]);
}
public function test_同じ学部内で重複した研究室名では作成できない(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
Lab::factory()->create([
'name' => 'テスト研究室',
'faculty_id' => $faculty->id,
]);
// Act
$response = $this->actingAs($user)
->post(route('labs.store', $faculty), [
'name' => 'テスト研究室',
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
]);
// Assert
$response->assertSessionHasErrors('name');
$this->assertDatabaseCount('labs', 1);
}
// -------------------------
// update
// -------------------------
public function test_認証済みユーザーが研究室情報を更新できる(): void
{
// Arrange
$user = User::factory()->create();
$lab = Lab::factory()->create([
'name' => '更新前研究室',
'version' => 1,
]);
// Act
$response = $this->actingAs($user)
->put(route('labs.update', $lab), [
'name' => '更新後研究室',
'description' => null,
'url' => null,
'professor_name' => null,
'professor_url' => null,
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
'comment' => '名称変更のため更新',
'version' => 1,
]);
// Assert
$response->assertRedirect(route('labs.show', $lab));
$this->assertDatabaseHas('labs', [
'id' => $lab->id,
'name' => '更新後研究室',
'version' => 2,
]);
}
public function test_バージョン不一致の場合は更新できない(): void
{
// Arrange
$user = User::factory()->create();
$lab = Lab::factory()->create([
'name' => '更新前研究室',
'version' => 2,
]);
// Act(古いバージョン番号を送信)
$response = $this->actingAs($user)
->put(route('labs.update', $lab), [
'name' => '更新後研究室',
'description' => null,
'url' => null,
'professor_name' => null,
'professor_url' => null,
'gender_ratio_male' => 6,
'gender_ratio_female' => 4,
'comment' => '変更理由',
'version' => 1,
]);
// Assert
$response->assertSessionHasErrors('version');
$this->assertDatabaseHas('labs', [
'id' => $lab->id,
'name' => '更新前研究室',
]);
}
// -------------------------
// history
// -------------------------
public function test_研究室の編集履歴を表示できる(): void
{
// Arrange
$user = User::factory()->create();
$lab = Lab::factory()->create();
$lab->users()->attach($user->id, [
'comment' => '初回登録',
'created_at' => now(),
'updated_at' => now(),
]);
// Act
$response = $this->get(route('labs.history', $lab));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Lab/History')
->has('editHistory', 1)
);
}
}
保存処理がやや複雑ですが、基本的には大学・学部と考え方は一緒です。
また、前回レビューコントローラーを作った際にはできなかった、レビューの一覧表示もshowセクションの中で行っています。
実行コマンド
$ php artisan test --filter=LabControllerTest
4. まとめ・次回予告
今日は、Featureテスト二日目ということで、大学・学部・研究室コントローラーについてのテストケースを作成しました!
次回は、残りのコントローラーに関してのテストケースを作成し行きましょう!
これまでの記事一覧
☆要件定義・設計編
☆環境構築編
☆バックエンド実装編
- その7: バックエンド実装編① ~認証機能作成~
- その8: バックエンド実装編②前編 ~レビュー投稿機能作成~
- その8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~
- その9: バックエンド実装編③ ~レビューCRUD機能作成~
- その10: バックエンド実装編④ ~レビューCRUD機能作成その2~
- その11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~
- その12: バックエンド実装編⑥ ~大学検索機能作成~
- その13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~
- その14: バックエンド実装編⑧ ~コメント投稿機能~
- その15: バックエンド実装編⑨ ~コメント編集・削除機能~
- その16: バックエンド実装編⑩ ~ブックマーク機能~
- その17: バックエンド実装編⑪ ~排他制御・トランザクション処理~
- その18: バックエンド実装編⑫ ~マイページ機能作成~
- その19: バックエンド実装編⑬ ~管理者アカウント機能作成~
- その20: バックエンド実装編⑭ ~通知機能作成~
- その21: バックエンド実装編⑮ ~ソーシャルログイン機能作成~
☆フロントエンド実装編
- その22: フロントエンド実装編① ~メインコンテンツ領域作成~
- その23: フロントエンド実装編② ~ヘッダー作成~
- その24: フロントエンド実装編③ ~サイドバー作成~
- その25: フロントエンド実装編④ ~ホームページ作成~
- その26: フロントエンド実装編⑤ ~大学検索結果画面作成~
- その27: フロントエンド実装編⑥ ~大学詳細・学部一覧画面作成~
- その28: フロントエンド実装編⑦ ~学部詳細・研究室一覧画面作成~
- その29: フロントエンド実装編⑧前編 ~研究室詳細・レビュー画面作成前編~
- その29.5: フロントエンド実装編⑧後編 ~研究室詳細・レビュー画面作成後編~
- その30: フロントエンド実装編⑨ ~マイページ作成~
- その31: フロントエンド実装編⑩ ~パンくずリスト作成~
- その32: フロントエンド実装編⑪ ~ログイン・新規登録モーダル作成~
- その33: フロントエンド実装編⑫ ~大学作成・編集モーダル作成~
- その34: フロントエンド実装編⑬ ~学部作成・編集モーダル作成~
- その35: フロントエンド実装編⑭ ~研究室作成・編集モーダル作成~
- その36: フロントエンド実装編⑮ ~レビュー作成・編集モーダル作成~
- その37: フロントエンド実装編⑯ ~コメント一覧表示モーダル作成~
- その38: フロントエンド実装編⑰ ~編集履歴ページ・削除依頼ページ・通知ドロップダウン作成~
- その39: フロントエンド実装編⑱ ~トースト作成~
- その40: フロントエンド実装編⑲ ~退会ページ作成~
- その41: フロントエンド実装編⑳ ~ファビコン作成~
- その42: フロントエンド実装編㉑ ~レスポンシブデザイン対応~


