実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その48)
0. 初めに
こんにちは!
Webアプリケーションを一から開発しているシリーズ、テスト編です!
※前回デプロイまでできたと言いましが、記事のシリーズはまだ続いているので引き続き週1回ペースで投稿していきます。
今回は、Featureテスト三日目ということで、コントローラーの残りを行っていきます。
1. マイページコントローラー
今日の最初は、マイページコントローラーです。
1.1 デバッグ
テストをする前にいきなりデバッグですが...w
バグがあるわけではないのですが、マイページコントローラーのメソッドを軽~く修正しましょう。
まず、不要になって削除し忘れていた二つのメソッドを削除します。
editUsershowBookmarks
あとは、deleteUserとremoveBookmarkに型注釈をつけておきます。
/**
* ユーザーを削除する(退会処理)
*/
public function deleteUser(): RedirectResponse
{
/** @var User $user */
$user = Auth::user();
$user->delete();
return redirect()->route('home')->with('success', 'アカウントを削除しました');
}
/**
* ブックマーク解除する
*/
public function removeBookmark($bookmarksId): RedirectResponse
{
/** @var User $user */
$user = Auth::user();
$bookmark = $user->bookmarks()->findOrFail($bookmarksId);
$bookmark->delete();
return redirect()->route('mypage.bookmarks')->with('success', 'ブックマークを削除しました');
}
修正は以上です。
1.2 作成
では、作成します。
実行コマンド
$ php artisan make:test Controllers/MyPageControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\Bookmark;
use App\Models\Faculty;
use App\Models\Lab;
use App\Models\University;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Tests\TestCase;
class MyPageControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// showUser
// -------------------------
public function test_マイページを表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('MyPage/Index')
->where('user.id', $user->id)
);
}
public function test_未認証ユーザーはマイページを表示できない(): void
{
// Act
$response = $this->get(route('mypage.index'));
// Assert
$response->assertRedirect('/');
}
public function test_マイページに通知が含まれる(): void
{
// Arrange
$user = User::factory()->create();
$user->notifications()->create([
'id' => Str::uuid()->toString(),
'type' => 'TestNotification',
'data' => ['message' => 'テスト通知'],
]);
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('notifications', 1)
);
}
public function test_マイページに通知が0件の場合も表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('notifications', 0)
);
}
public function test_マイページにブックマーク済み研究室が含まれる(): void
{
// Arrange
$user = User::factory()->create();
$lab = Lab::factory()->create();
Bookmark::factory()->create([
'user_id' => $user->id,
'lab_id' => $lab->id,
]);
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('bookmarks', 1)
);
}
public function test_マイページにブックマークが0件の場合も表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('bookmarks', 0)
);
}
public function test_マイページに作成済みリソースが含まれる(): void
{
// Arrange
$user = User::factory()->create();
University::factory()->create(['created_by' => $user->id]);
Faculty::factory()->create(['created_by' => $user->id]);
Lab::factory()->create(['created_by' => $user->id]);
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('universities', 1)
->has('faculties', 1)
->has('createdLabs', 1)
);
}
public function test_マイページに作成済みリソースが0件の場合も表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('mypage.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('universities', 0)
->has('faculties', 0)
->has('createdLabs', 0)
);
}
// -------------------------
// updateUser
// -------------------------
public function test_ユーザー情報を更新できる(): void
{
// Arrange
$user = User::factory()->create(['name' => '更新前の名前']);
// Act
$response = $this->actingAs($user)
->put(route('mypage.update'), [
'nickname' => '更新後の名前',
'email' => $user->email,
]);
// Assert
$response->assertRedirect(route('mypage.index'));
$this->assertDatabaseHas('users', [
'id' => $user->id,
'name' => '更新後の名前',
]);
}
public function test_パスワードを更新できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)
->put(route('mypage.update'), [
'nickname' => $user->name,
'email' => $user->email,
'password' => 'newpassword123',
'password_confirmation' => 'newpassword123',
]);
// Assert
$response->assertRedirect(route('mypage.index'));
}
public function test_バリデーションエラーでユーザー情報を更新できない(): void
{
// Arrange
$user = User::factory()->create();
// Act(nickname が空)
$response = $this->actingAs($user)
->put(route('mypage.update'), [
'nickname' => '',
'email' => $user->email,
]);
// Assert
$response->assertSessionHasErrors('nickname');
}
// -------------------------
// showWithdrawal
// -------------------------
public function test_退会ページを表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('mypage.withdrawal'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page->component('MyPage/Withdrawal'));
}
// -------------------------
// deleteUser
// -------------------------
public function test_アカウントを削除できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->delete(route('mypage.delete'));
// Assert
$response->assertRedirect(route('home'));
// SoftDeletes のため物理削除ではなく deleted_at がセットされる
$this->assertNotNull($user->fresh()->deleted_at);
}
// -------------------------
// removeBookmark
// -------------------------
public function test_ブックマークを解除できる(): void
{
// Arrange
$user = User::factory()->create();
$bookmark = Bookmark::factory()->create(['user_id' => $user->id]);
// Act
$response = $this->actingAs($user)
->delete(route('mypage.bookmarks.remove', $bookmark));
// Assert
$response->assertRedirect(route('mypage.bookmarks'));
$this->assertDatabaseMissing('bookmarks', ['id' => $bookmark->id]);
}
public function test_他人のブックマークは解除できない(): void
{
// Arrange
$user = User::factory()->create();
$otherUser = User::factory()->create();
$bookmark = Bookmark::factory()->create(['user_id' => $otherUser->id]);
// Act
$response = $this->actingAs($user)
->delete(route('mypage.bookmarks.remove', $bookmark));
// Assert
$response->assertStatus(404);
$this->assertDatabaseHas('bookmarks', ['id' => $bookmark->id]);
}
}
showUserメソッドでは、そのユーザーへの通知、そのユーザーがブックマークした研究室、作成済みの大学・学部・研究室を表示します。
また、deleteUserメソッドは、ユーザーテーブルからレコードそのものを削除するいわゆる物理削除ではなく、削除されたかどうかを示すカラムであるdeleted_atにその日付を付与する論理削除を採用していました。
use HasFactory, Notifiable, SoftDeletes;
そのため、assertNotNullを用いて、更新された$userのdeleted_atカラムがNullではないことを確認しています。
1.3 実行
実行コマンド
$ php artisan test --filter=MyPageControllerTest
2. 削除依頼コントローラー
次に、削除依頼コントローラーに関するテストケースを作成します。
2.1 作成
実行コマンド
$ php artisan make:test Controllers/DeletionRequestControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\DeletionRequest;
use App\Models\University;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class DeletionRequestControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// create
// -------------------------
public function test_未認証ユーザーは削除依頼作成フォームを表示できない(): void
{
// Arrange
$university = University::factory()->create();
// Act
$response = $this->get(route('deletion_requests.create', ['type' => 'university', 'id' => $university->id]));
// Assert
$response->assertRedirect('/');
}
public function test_認証済みユーザーは削除依頼作成フォームを表示できる(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act
$response = $this->actingAs($user)
->get(route('deletion_requests.create', ['type' => 'university', 'id' => $university->id]));
// Assert
$response->assertOk();
}
// -------------------------
// store
// -------------------------
public function test_未認証ユーザーは削除依頼を送信できない(): void
{
// Arrange
$university = University::factory()->create();
// Act
$response = $this->post(route('deletion_requests.store'), [
'target_id' => $university->id,
'target_type' => 'university',
'reason' => 'テスト理由',
]);
// Assert
$response->assertRedirect('/');
$this->assertDatabaseMissing('deletion_requests', [
'target_id' => $university->id,
]);
}
public function test_認証済みユーザーは削除依頼を送信できる(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act
$response = $this->actingAs($user)
->post(route('deletion_requests.store'), [
'target_id' => $university->id,
'target_type' => 'university',
'reason' => 'テスト理由',
]);
// Assert
$response->assertOk();
$this->assertDatabaseHas('deletion_requests', [
'requested_by' => $user->id,
'target_id' => $university->id,
'status' => 'pending',
]);
}
public function test_バリデーションエラーで削除依頼が拒否される(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act(reason が空)
$response = $this->actingAs($user)
->post(route('deletion_requests.store'), [
'target_id' => $university->id,
'target_type' => 'university',
'reason' => '',
]);
// Assert
$response->assertSessionHasErrors('reason');
$this->assertDatabaseMissing('deletion_requests', [
'requested_by' => $user->id,
]);
}
// -------------------------
// index
// -------------------------
public function test_未認証ユーザーは削除依頼一覧を表示できない(): void
{
// Act
$response = $this->get(route('admin.deletion_requests.index'));
// Assert
$response->assertRedirect('/');
}
public function test_一般ユーザーは削除依頼一覧を表示できない(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)
->get(route('admin.deletion_requests.index'));
// Assert
$response->assertForbidden();
}
public function test_管理者は削除依頼一覧を表示できる(): void
{
// Arrange
$admin = User::factory()->create(['is_admin' => true]);
// Act
$response = $this->actingAs($admin)
->get(route('admin.deletion_requests.index'));
// Assert
$response->assertOk();
}
}
削除依頼の作成と送信された削除依頼一覧の閲覧です。
削除依頼一覧は、管理者しか見られてはいけないはずですね。
2.2 実行
実行コマンド
$ php artisan test --filter=DeletionRequestControllerTest
どうやら一つ通っていないようです。
2.3 デバッグ
では、原因を直しましょう。
test_一般ユーザーは削除依頼一覧を表示できないはずが、できてしまっていますね。
コントローラーのindexメソッドを見てみると認可の処理が一切ありません。
そのため、いっんユーザーでも削除依頼一覧が見られてしまいます。
// 管理者に削除依頼を表示
public function index()
{
$deletionRequests = DeletionRequest::with(['requester', 'target'])
->where('status', 'pending')
->latest()
->get();
return Inertia::render('DeletionRequest/Index', [
'deletionRequests' => $deletionRequests,
]);
}
修正です!
<?php
namespace App\Http\Controllers;
use App\Models\DeletionRequest;
use App\Models\Faculty;
use App\Models\Lab;
use App\Models\University;
use App\Models\User;
use App\Notifications\DeletionRequestNotification;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class DeletionRequestController extends Controller
{
/**
* 削除依頼フォームを表示する
*/
public function create(string $type, int $id): Response
{
$query = request('query', '');
$model = match ($type) {
'university' => University::find($id),
'faculty' => Faculty::find($id),
'lab' => Lab::find($id),
};
$backUrl = match ($type) {
'university' => route('faculties.index', ['university' => $model->id]),
'faculty' => route('labs.index', ['faculty' => $model->id]),
'lab' => route('labs.show', ['lab' => $model->id]),
};
return Inertia::render('DeletionRequest/Create', [
'target' => [
'id' => $model->id,
'name' => $model->name,
'type' => $type,
],
'backUrl' => $backUrl,
'query' => $query,
]);
}
/**
* 削除依頼を保存する
*/
public function store(Request $request): Response
{
$validated = $request->validate([
'target_id' => 'required|integer',
'target_type' => 'required|string|in:university,faculty,lab',
'reason' => 'required|string|max:1000',
]);
$model = match ($validated['target_type']) {
'university' => University::class,
'faculty' => Faculty::class,
'lab' => Lab::class,
};
$deletionRequests = new DeletionRequest();
$deletionRequests->requested_by = $request->user()->id;
$deletionRequests->target_id = $validated['target_id'];
$deletionRequests->target_type = $model;
$deletionRequests->reason = $validated['reason'] ?? null;
$deletionRequests->status = 'pending';
$deletionRequests->save();
$admins = User::where('is_admin', true)->get();
foreach ($admins as $admin) {
$admin->notify(new DeletionRequestNotification(
$model::find($validated['target_id'])->name,
$validated['target_type'],
$validated['target_id']
));
}
return Inertia::render('DeletionRequest/Complete');
}
/**
* 管理者に削除依頼を表示
*/
public function index(): Response
{
abort_unless(auth()->user()->is_admin(), 403);
$deletionRequests = DeletionRequest::with(['requester', 'target'])
->where('status', 'pending')
->latest()
->get();
return Inertia::render('DeletionRequest/Index', [
'deletionRequests' => $deletionRequests,
]);
}
}
abort_unlessという第一引数がfalseの場合に第二引数に指定した例外レスポンスを返すLaravelの便利関数を呼び出してみました。
何度も登場しているかとは思いますが、403認可NGのステータスコードです。
ついでに、戻り値の型注釈と他のメソッドへのコメントアウトも付与しておきましたので併せてご確認ください。(笑)
2.4 再実行
実行コマンド
$ php artisan test --filter=DeletionRequestControllerTest
3. 通知コントローラー
続いて、通知コントローラーです。
3.1 作成
実行コマンド
$ php artisan make:test Controllers/NotificationControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Tests\TestCase;
class NotificationControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// index
// -------------------------
public function test_未認証ユーザーは通知一覧を表示できない(): void
{
// Act
$response = $this->get(route('notifications.index'));
// Assert
$response->assertRedirect('/');
}
public function test_認証済みユーザーは通知一覧を表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('notifications.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Notification/Index')
->where('user.id', $user->id)
);
}
public function test_通知が0件でも表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('notifications.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('notifications', 0)
);
}
public function test_通知が含まれる場合も表示できる(): void
{
// Arrange
$user = User::factory()->create();
$user->notifications()->create([
'id' => Str::uuid()->toString(),
'type' => 'TestNotification',
'data' => ['message' => 'テスト通知'],
]);
// Act
$response = $this->actingAs($user)->get(route('notifications.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('notifications', 1)
);
}
public function test_他のユーザーの通知は表示されない(): void
{
// Arrange
$user = User::factory()->create();
$otherUser = User::factory()->create();
$otherUser->notifications()->create([
'id' => Str::uuid()->toString(),
'type' => 'TestNotification',
'data' => ['message' => '他ユーザーの通知'],
]);
// Act
$response = $this->actingAs($user)->get(route('notifications.index'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->has('notifications', 0)
);
}
// -------------------------
// markAsRead
// -------------------------
public function test_未認証ユーザーは既読にできない(): void
{
// Act
$response = $this->post(route('notifications.markAsRead'));
// Assert
$response->assertRedirect('/');
}
public function test_認証済みユーザーは未読通知を既読にできる(): void
{
// Arrange
$user = User::factory()->create();
$user->notifications()->create([
'id' => Str::uuid()->toString(),
'type' => 'TestNotification',
'data' => ['message' => 'テスト通知'],
]);
// Act
$response = $this->actingAs($user)->post(route('notifications.markAsRead'));
// Assert
$response->assertRedirect();
$this->assertCount(0, $user->fresh()->unreadNotifications);
}
public function test_既読にできるのは自分の通知のみで他ユーザーの通知は変わらない(): void
{
// Arrange
$user = User::factory()->create();
$otherUser = User::factory()->create();
$otherUser->notifications()->create([
'id' => Str::uuid()->toString(),
'type' => 'TestNotification',
'data' => ['message' => '他ユーザーの通知'],
]);
// Act
$this->actingAs($user)->post(route('notifications.markAsRead'));
// Assert(他ユーザーの通知は既読にならない)
$this->assertCount(1, $otherUser->unreadNotifications);
}
}
3.2 実行
実行コマンド
$ php artisan test --filter=NotificationControllerTest
4. ホームコントローラー
最後は、ホームコントローラーです。
4.1 作成
実行コマンド
$ php artisan make:test Controllers/HomeControllerTest
<?php
namespace Tests\Feature\Controllers;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class HomeControllerTest extends TestCase
{
use RefreshDatabase;
public function test_未認証ユーザーはホームページを表示できる(): void
{
// Act
$response = $this->get(route('home'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page->component('Home'));
}
public function test_認証済みユーザーもホームページを表示できる(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)->get(route('home'));
// Assert
$response->assertOk();
$response->assertInertia(fn ($page) => $page->component('Home'));
}
}
認証も認可もいらないので簡単ですね。
4.2 実行
実行コマンド
$ php artisan test --filter=HomeControllerTest
5. プロフィールコントローラー
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;
use Inertia\Response;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): Response
{
return Inertia::render('Profile/Edit', [
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
'status' => session('status'),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}
このコントローラーはLaravelに最初からついていたもので、ユーザー情報の更新や削除処理が書かれています。
しかし、これらの処理はマイページコントローラーに集約されておりもはや不要になりましたので、このファイル自体を削除してしまいましょう。
それに伴って、ルーティングからも削除してしまいましょう。
use App\Http\Controllers\ProfileController; // ←削除
Route::middleware('auth')->group(function () {
// ↓削除
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// レビュー関連...
よって、テストもなしです。
削除しましょう。
削除: tests/Feature/ProfileTest.php
6. 管理者コントローラー
最後はラスボスの管理者コントローラーを始末しましょう。
6.1 作成
実行コマンド
$ php artisan make:test Controllers/Admin/AdminControllerTest
<?php
namespace Tests\Feature\Controllers\Admin;
use App\Models\Comment;
use App\Models\Faculty;
use App\Models\Lab;
use App\Models\University;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminControllerTest extends TestCase
{
use RefreshDatabase;
// -------------------------
// destroyUniversity
// -------------------------
public function test_管理者は大学を削除できる(): void
{
// Arrange
$admin = User::factory()->create(['is_admin' => true]);
$university = University::factory()->create();
// Act
$response = $this->actingAs($admin)
->delete(route('admin.universities.destroy', $university));
// Assert
$response->assertRedirect(route('home'));
$this->assertNotNull($university->fresh()->deleted_at);
}
public function test_一般ユーザーは大学を削除できない(): void
{
// Arrange
$user = User::factory()->create();
$university = University::factory()->create();
// Act
$response = $this->actingAs($user)
->delete(route('admin.universities.destroy', $university));
// Assert
$response->assertForbidden();
$this->assertDatabaseHas('universities', ['id' => $university->id]);
}
public function test_未認証ユーザーは大学を削除できない(): void
{
// Arrange
$university = University::factory()->create();
// Act
$response = $this->delete(route('admin.universities.destroy', $university));
// Assert
$response->assertRedirect('/');
$this->assertDatabaseHas('universities', ['id' => $university->id]);
}
// -------------------------
// destroyFaculty
// -------------------------
public function test_管理者は学部を削除できる(): void
{
// Arrange
$admin = User::factory()->create(['is_admin' => true]);
$faculty = Faculty::factory()->create();
// Act
$response = $this->actingAs($admin)
->delete(route('admin.faculties.destroy', $faculty));
// Assert
$response->assertRedirect(route('faculties.index', ['university' => $faculty->university_id]));
$this->assertNotNull($faculty->fresh()->deleted_at);
}
public function test_一般ユーザーは学部を削除できない(): void
{
// Arrange
$user = User::factory()->create();
$faculty = Faculty::factory()->create();
// Act
$response = $this->actingAs($user)
->delete(route('admin.faculties.destroy', $faculty));
// Assert
$response->assertForbidden();
$this->assertDatabaseHas('faculties', ['id' => $faculty->id]);
}
public function test_未認証ユーザーは学部を削除できない(): void
{
// Arrange
$faculty = Faculty::factory()->create();
// Act
$response = $this->delete(route('admin.faculties.destroy', $faculty));
// Assert
$response->assertRedirect('/');
$this->assertDatabaseHas('faculties', ['id' => $faculty->id]);
}
// -------------------------
// destroyLab
// -------------------------
public function test_管理者は研究室を削除できる(): void
{
// Arrange
$admin = User::factory()->create(['is_admin' => true]);
$lab = Lab::factory()->create();
// Act
$response = $this->actingAs($admin)
->delete(route('admin.labs.destroy', $lab));
// Assert
$response->assertRedirect(route('labs.index', ['faculty' => $lab->faculty_id]));
$this->assertNotNull($lab->fresh()->deleted_at);
}
public function test_一般ユーザーは研究室を削除できない(): void
{
// Arrange
$user = User::factory()->create();
$lab = Lab::factory()->create();
// Act
$response = $this->actingAs($user)
->delete(route('admin.labs.destroy', $lab));
// Assert
$response->assertForbidden();
$this->assertDatabaseHas('labs', ['id' => $lab->id]);
}
public function test_未認証ユーザーは研究室を削除できない(): void
{
// Arrange
$lab = Lab::factory()->create();
// Act
$response = $this->delete(route('admin.labs.destroy', $lab));
// Assert
$response->assertRedirect('/');
$this->assertDatabaseHas('labs', ['id' => $lab->id]);
}
// -------------------------
// destroyComment
// -------------------------
public function test_管理者は任意のコメントを削除できる(): void
{
// Arrange
$admin = User::factory()->create(['is_admin' => true]);
$comment = Comment::factory()->create();
// Act
$response = $this->actingAs($admin)
->delete(route('admin.comments.destroy', $comment));
// Assert
$response->assertRedirect(route('labs.show', ['lab' => $comment->lab_id]));
$this->assertDatabaseMissing('comments', ['id' => $comment->id]);
}
public function test_一般ユーザーは他人のコメントを削除できない(): void
{
// Arrange
$user = User::factory()->create();
$otherUser = User::factory()->create();
$comment = Comment::factory()->create(['user_id' => $otherUser->id]);
// Act
$response = $this->actingAs($user)
->delete(route('admin.comments.destroy', $comment));
// Assert
$response->assertForbidden();
$this->assertDatabaseHas('comments', ['id' => $comment->id]);
}
public function test_未認証ユーザーはコメントを削除できない(): void
{
// Arrange
$comment = Comment::factory()->create();
// Act
$response = $this->delete(route('admin.comments.destroy', $comment));
// Assert
$response->assertRedirect('/');
$this->assertDatabaseHas('comments', ['id' => $comment->id]);
}
}
6.2 実行
実行コマンド
$ php artisan test --filter=AdminControllerTest
お疲れ様です!!
7. デグレ確認
色々と修正したので、過去には通っていたテストが通らなくなってしまう、いわゆるデグレードが起きていないかを最後に確認して終わりたいと思います。
まとめて実行しましょう。
実行コマンド
$ php artisan test tests/Feature/Controllers
時間はかかりますが、少しだけ待てばすべて通ることが確認できるはずです!
ここまでたどり着けたら、コミット・プッシュ、PR作成・マージ、ブランチ削除までしちゃいましょう!!
8. まとめ・次回予告
大変お疲れ様でした。(^^♪
これにて、コントローラーに関するFeatureテストのテストケース作成・テスト実行・デバッグが完了です!
次回は、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: フロントエンド実装編㉑ ~レスポンシブデザイン対応~






