dbudwin's avatar

Test middleware that relies on sessions

I want to write a unit test to make sure my middleware redirects users as expected. My middleware checks to see if a user is logged in by verifying a token is set in the session. The session isn't available in the test. I've tried a lot, the most promising idea I came across was to store the session in an in-memory array, but I can't get it to work.

The middleware looks like:

class RedirectIfAuthenticated
{
    public function handle($request, Closure $next)
    {
        if ($request->session()->has(env('SESSION_USER_ID'))) {
            return $next($request);
        }

        return redirect()->route('index');
    }
}

The unit test I developed (I've been trying a lot of things as you'll see) to test the middleware looks like:

public function testExample()
{
    //$this->withSession([env('SESSION_USER_ID') => '1234']);

    //SessionManager::setDefaultDriver('array');
    //$this->manager = app('session');

    //Config::set('session.driver', 'array');
    //Config::set('session.driver', 'array');

    //Session::start();
    //$this->app['config']['session.driver'] = 'array';

    $request = Request::create('/', 'GET');
    $this->session([env('SESSION_USER_ID') => 1234]);
    //$session = $request->session();
    //$session->put([env('SESSION_USER_ID') => '1234']);

    $middleware = new RedirectIfAuthenticated();
    $response = $middleware->handle($request, function () {
        $this->assertEquals($response->getStatusCode(), 302);
    });
}

However, I get the following error and the test fails:

Session store not set on request.

How can I develop a middleware test where I can access the session?

0 likes
1 reply
ChristophHarms's avatar

EDIT: Ugh, wtf, 2 years ago? How did this show up on the first page of my "no replies yet" section? o.Ô

If you really want to unit test the Middleware, you'll have to mock the request:

public function testExample()
{
    $requestMock = \Mockery::mock(Illuminate\Http\Request::class);
    $requestMock->shouldReceive('session')
        // just return an anonymous dummy class that knows the has() method and 
        // returns true or false depending on our needs. Alternative would be
        // to also mock the session and return the session mock.
        ->andReturn(new class
        {
            public function has(string $key)
            {
                return true; // or false, depends on what you want to test
            }
        });

    $middleware = new RedirectIfAuthenticated;
    $response = $middleware->handle($requestMock, function ($request) {
        return 'I have not been redirected',
    });

    // If you want to test that no redirection has taken place
    $this->assertEquals('I have not been redirected', $response);

    // If you want to test that redirection did take place
    $this->assertInstanceOf(\Illuminate\Http\RedirectResponse::class, $response);
}

On second thought, even the request mock is unnecessary and could be replaced by an anonymous class, since it is not typechecked in the middleware.

BUT

This seems overly complicated. Why not just make it an integration test instead of a unit test and use laravel's fluent api for testing requests?

public function testExample()
{
    $this->withSession([env('SESSION_USER_ID') => '1234'])
        ->get('some/url')
        ->assertStatus(200); // no redirection

    $this->withSession([])
        ->get('some/url')
        ->assertStatus(302); // redirection
}

Please or to participate in this conversation.