'Laravel actingAs guest

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?



Solution 1:[1]

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.

Solution 2:[2]

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();
    }
}

Solution 3:[3]

I had same requirements as OP did, but wanted actingAsGuest() to completely reset everything, except Database state.

Full App reset (except DB)

For Laravel 7 (and maybe newer or older)

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;
    });
}

WARNING !!

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');

   // ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Top-Master
Solution 2 Lukas Pierce
Solution 3