30 Days of Automated Testing:Using PHPUnit【D10】

Auth Testing

WilliamP
3 min readJan 23, 2023

In the previous article, we introduced Traits commonly used in writing automated tests. Today, we will be discussing how to test Auth-related functionality and demonstrating the use of the RefreshDatabase and WithoutMiddlewareTraits.

Retrieve The Current Logged-In User’s Information.

In web development using Laravel, it is common to use the Auth::user() method to obtain the user data associated with the current login session or API request token. However, when writing tests, it can be cumbersome to have to simulate inputting credentials and submitting login requests, and then extracting the session ID cookie or API token from the response to include in the actual test behavior. To address this, Laravel provides two functions:

  • $this->actingAs(UserContract $user, $guard = null)
  • $this->be(UserContract $user, $guard = null)

These two functions are completely the same (in fact, $this->actingAs() is just calling $this->be()), they both allow us to set the current user that we want to simulate login/Auth for. Let's take a look at how to use it! (Since the official documentation mainly uses $this->actingAs(), the following examples will also mainly use $this->actingAs() )

  • routes/web.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::get('/me/profiles', function (Request $request) {
$user = Auth::user();

$profile = [
'name' => $user->name,
'email' => $user->email,
];

return response()->json(['profile' => $profile]);
})->name('get.me.profile')
->middleware(['auth']);

In the above code, we have implemented an API endpoint that retrieves the personal profile data of the currently logged-in user and responds in JSON format.

  • tests/Feature/AuthTest.php
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AuthTest extends TestCase
{
use RefreshDatabase

public function testGetLoggedInUserProfile()
{
$user = User::factory()->create();

$this->actingAs($user)->get(route('get.me.profile'))
->assertJson([
'profile' => [
'name' => $user->name,
'email' => $user->email,
]
]);
}
}

The above test code, we created a fake data $user, and set it as the current logged-in User, and tested whether the user's personal profile information can be obtained through the get.me.profile API endpoint. At the same time, because we have created fake data, in order not to let other test cases interact with this test case, we use the RefreshDatabase Trait, resetting the database before and after each test in tests/Feature/AuthTest.php is executed.

Skip Middleware

In the previous article, it was mentioned that sometimes the functionality we want to test goes through some Middleware processing, and it may require some special configurations in order to use the desired functionality normally. However, the processing logic of the Middleware may not be the focus of the current test, and in this case, the WithoutMiddleware can be used to bypass the Middleware.

  • app/Http/Middleware/ForbidLoginDuringLunch.php
<?php

namespace App\Http\Middleware;

use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;

class ForbidLoginDuringLunch
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$hour = Carbon::now()->hour;

if ($hour <= 14 && $hour >= 12) {
return response()->json(['error' => 'No lunch login'], 403);
}

return $next($request);
}
}

The above code implements a Middleware, which checks whether the current time is between 12~14 hours, if it is, it will respond with 403, which is equivalent to prohibiting login to the website during lunchtime (is it possible for a website to implement such a Middleware?).

  • routes/web.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::get('/me/profiles', function (Request $request) {
$user = Auth::user();

$profile = [
'name' => $user->name,
'email' => $user->email,
];

return response()->json(['profile' => $profile]);
})->name('get.me.profile')
->middleware(['auth', 'no.lunch.login']); // 新增 no.lunch.login Middleware

The above code is similar to the previous example, but with the additional use of the no.lunch.loginmiddleware.

  • tests/Feature/AuthTest.php
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class AuthTest extends TestCase
{
use RefreshDatabase;
use WithoutMiddleware;

public function testGetLoggedInUserProfile()
{
$user = User::factory()->create();

$this->actingAs($user)->get(route('get.me.profile'))
->assertJson([
'profile' => [
'name' => $user->name,
'email' => $user->email,
]
]);
}
}

The above code is similar to the previous example, but uses the WithoutMiddleware, after using it, the test can proceed smoothly!

The above is today’s introduction! I wonder if you can digest it? (At the time, it took the author quite a bit of time to understand…)

Next time, let’s test “commands”!

If you liked this article or found it helpful, feel free to give it some claps and follow the author!

Reference

Articles of This Series

--

--