0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(テスト・デバッグ編⑥)~Featureテスト3[コントローラー③/マイページ/削除依頼/通知/ホーム/管理者]~

0
Posted at

実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その48)

0. 初めに

こんにちは!
Webアプリケーションを一から開発しているシリーズ、テスト編です!

※前回デプロイまでできたと言いましが、記事のシリーズはまだ続いているので引き続き週1回ペースで投稿していきます。

今回は、Featureテスト三日目ということで、コントローラーの残りを行っていきます。

1. マイページコントローラー

今日の最初は、マイページコントローラーです。

1.1 デバッグ

テストをする前にいきなりデバッグですが...w

バグがあるわけではないのですが、マイページコントローラーのメソッドを軽~く修正しましょう。

まず、不要になって削除し忘れていた二つのメソッドを削除します。

  • editUser
  • showBookmarks

あとは、deleteUserremoveBookmarkに型注釈をつけておきます。

\project-root\src\app\Http\Controllers\MyPageController.php
    /**
     * ユーザーを削除する(退会処理)
     */
    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 作成

では、作成します。

実行コマンド

/var/www
$ php artisan make:test Controllers/MyPageControllerTest
\project-root\src\tests\Feature\Controllers\MyPageControllerTest.php
<?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にその日付を付与する論理削除を採用していました。

\project-root\src\app\Models\User.php
    use HasFactory, Notifiable, SoftDeletes;

そのため、assertNotNullを用いて、更新された$userdeleted_atカラムがNullではないことを確認しています。

1.3 実行

実行コマンド

/var/www
$ php artisan test --filter=MyPageControllerTest

実行結果
image.png

2. 削除依頼コントローラー

次に、削除依頼コントローラーに関するテストケースを作成します。

2.1 作成

実行コマンド

/var/www
$ 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 実行

実行コマンド

/var/www
$ php artisan test --filter=DeletionRequestControllerTest

実行結果
image.png

どうやら一つ通っていないようです。

2.3 デバッグ

では、原因を直しましょう。

test_一般ユーザーは削除依頼一覧を表示できないはずが、できてしまっていますね。

コントローラーのindexメソッドを見てみると認可の処理が一切ありません。
そのため、いっんユーザーでも削除依頼一覧が見られてしまいます。

    // 管理者に削除依頼を表示
    public function index()
    {
        $deletionRequests = DeletionRequest::with(['requester', 'target'])
            ->where('status', 'pending')
            ->latest()
            ->get();

        return Inertia::render('DeletionRequest/Index', [
            'deletionRequests' => $deletionRequests,
        ]);
    }

修正です!

\project-root\src\app\Http\Controllers\DeletionRequestController.php
<?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 再実行

実行コマンド

/var/www
$ php artisan test --filter=DeletionRequestControllerTest

実行結果
image.png

3. 通知コントローラー

続いて、通知コントローラーです。

3.1 作成

実行コマンド

/var/www
$ php artisan make:test Controllers/NotificationControllerTest
\project-root\src\tests\Feature\Controllers\NotificationControllerTest.php
<?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 実行

実行コマンド

/var/www
$ php artisan test --filter=NotificationControllerTest

実行結果
image.png

4. ホームコントローラー

最後は、ホームコントローラーです。

4.1 作成

実行コマンド

/var/www
$ php artisan make:test Controllers/HomeControllerTest
\project-root\src\tests\Feature\Controllers\HomeControllerTest.php
<?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 実行

実行コマンド

/var/www
$ php artisan test --filter=HomeControllerTest

実行結果
image.png

5. プロフィールコントローラー

\project-root\src\app\Http\Controllers\ProfileController.php
<?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に最初からついていたもので、ユーザー情報の更新や削除処理が書かれています。

しかし、これらの処理はマイページコントローラーに集約されておりもはや不要になりましたので、このファイル自体を削除してしまいましょう。

それに伴って、ルーティングからも削除してしまいましょう。

\project-root\src\routes\web.php
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 作成

実行コマンド

/var/www
$ php artisan make:test Controllers/Admin/AdminControllerTest
\project-root\src\tests\Feature\Controllers\Admin\AdminControllerTest.php
<?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 実行

実行コマンド

/var/www
$ php artisan test --filter=AdminControllerTest

実行結果
image.png

お疲れ様です!!

7. デグレ確認

色々と修正したので、過去には通っていたテストが通らなくなってしまう、いわゆるデグレードが起きていないかを最後に確認して終わりたいと思います。

まとめて実行しましょう。
実行コマンド

/var/www
$ php artisan test tests/Feature/Controllers

実行結果
image.png

時間はかかりますが、少しだけ待てばすべて通ることが確認できるはずです!

ここまでたどり着けたら、コミット・プッシュ、PR作成・マージ、ブランチ削除までしちゃいましょう!!

8. まとめ・次回予告

大変お疲れ様でした。(^^♪

これにて、コントローラーに関するFeatureテストのテストケース作成・テスト実行・デバッグが完了です!

次回は、Featureテストの中でも残っているところや後回しにした部分に取り掛かりたいと思います。

これまでの記事一覧

☆要件定義・設計編

☆環境構築編

☆バックエンド実装編

☆フロントエンド実装編

☆テスト・デバッグ編

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?