Laravel provides a way to authenticate a given user during HTTP testing with
$this->actingAs($user);
Is there a way to unauthenticate that $user within the same test?
Laravel provides a way to authenticate a given user during HTTP testing with
$this->actingAs($user);
Is there a way to unauthenticate that $user within the same test?
Yes, you can unauthenticate using this:
Auth::logout();
https://laravel.com/docs/7.x/authentication#logging-out
Warning: above does far more than just forgetting (acting as if the login did not happen), for example, when using JWT above should invalidate token.
Yes, define new actingAsGuest method in base TestCase class
in file tests/TestCase.php
<?php
namespace Tests;
use Illuminate\Auth\RequestGuard;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function setUp(): void
{
parent::setUp();
// add logout method to RequestGuard
RequestGuard::macro('logout', function() {
$this->user = null;
});
}
// add method to base TestCase class
public function actingAsGuest(): void
{
$this->app['auth']->logout();
}
}
And then in your test class you can use it:
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function test_example()
{
// acting as authenticated user
$this->actingAs(User::factory()->create());
$this->assertAuthenticated();
// acting as unauthenticated user
$this->actingAsGuest();
$this->assertGuest();
}
}
I had same requirements as OP did, but wanted actingAsGuest() to completely reset everything, except Database state.
I toke a look at Laravel's tearDown() and setUp() methods.
And came up with helper method like:
use Illuminate\Support\Facades\Facade;
// ...
function actingAsGuest()
{
// Backup database state.
/** @var \Illuminate\Database\MySqlConnection $connection */
$connection = app('db.connection');
// Reset everything else.
/** @var \Illuminate\Foundation\Application $app */
$app = $this->app;
$app->flush();
$this->app = null;
Facade::clearResolvedInstances();
$this->refreshApplication();
// Restore database state.
app('db')->extend($connection->getName(), function () use ($connection) {
return $connection;
});
}
Above works fine unless your test's logic caches any of above discarded objects somewhere.
For example, Laravel's DatabaseTransactions trait did cache db facade (in their App-Destroyed-listener).
Which we fixed by overriding said trait's logic.
Like we changed:
// ... $this->beforeApplicationDestroyed(function () use ($database) { // ...Into:
// ... $this->beforeApplicationDestroyed(function () { $database = app('db'); // ...