Jozsef Hocza

Day 1: lumen-jwt - testing tymon's JWTAuth

The Quest

So I chose the krisanalfa/lumen-jwt boilerplate to start with. I hoped it won’t turn out to be a bad idea.

What just happend? Turned out to be a bad idea. I wanted to make some tests, but failed to make those tests.

<?php

public function testUserLogin()
{
    $user = factory('App\User')->create();

    $this->post('/api/auth/login', ['email' => $user->email, 'password' => 1234])->seeJson([
            'message' => 'token_generated',
        ]);
    $token = json_decode($this->response->getContent())->data->token;

    $this->get('/api/auth/user', ['Authorization' => 'Bearer '.$token])
        ->seeJson(['message' => 'authenticated_user']);
}

Here is what happened:

1) UserRegAndAuthTest::testUserLogin
Unable to find JSON fragment ["message":"authenticated_user"] within [{"message":"Failed to authenticate because of bad credentials or an invalid authorization header.","status_code":401}].
Failed asserting that false is true.

I was like: WHAT THE HECK. I started googling to see if anyone else bounced right into this issue. Sure they did!

They quickly suggested using: $this->refreshApplication() right after the first test. Well okay!

1) UserRegAndAuthTest::testUserLogin
Unable to find JSON fragment ["message":"authenticated_user"] within [{"code":"HY000","message":"SQLSTATE[HY000]: General error: 1 no such table: users (SQL: select * from \"users\" where \"users\".\"id\" = 1 limit 1)","status_code":500}].

Thanks for the following phpunit.xml settings it also dropped my in-memory database.

...
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
...

After freaking out I was like: well okay… let’s store it in file:
<env name="DB_DATABASE" value="/tmp/FCKINFCKINCRAP.sqlite"/>

You can guess what happened:

1) UserRegAndAuthTest::testUserLogin
Unable to find JSON fragment ["message":"authenticated_user"] within [{"message":"The token could not be parsed from the request","status_code":500}].
Failed asserting that false is true.

I was again… crawling the wall… but hey, let’s debug it.

It seems at the first error, it happened on the Dingo API. The refresh fixed it, but dropped the in-memory database. After I fixed the second problem, the third problem did not happen because of Dingo. It happened because of this:

<?php

public function getUser()
{
    return new JsonResponse([
        'message' => 'authenticated_user',
        'data' => JWTAuth::parseToken()->authenticate() /* because of this line */
    ]);
}

The JWAuth facade is not receiving the Request $request if you’re running phpunit tests. How intriguing…

It works on Dingo API but not inside the IoC.

After hours of browsing I had no luck. So I thought about the problem. It does not have the Request. So I finally come up with an idea to fix this problem.

Here is the fix that you need to put inside of Http/Controllers/Controller

<?php

namespace App\Http\Controllers;

use Dingo\Api\Routing\Helpers;
use Illuminate\Http\Request;
use Laravel\Lumen\Routing\Controller as BaseController;
use Tymon\JWTAuth\Facades\JWTAuth;

class Controller extends BaseController
{
    use Helpers;

    /* This is the GOD DAMN FIX */
    public function __construct(Request $request)
    {
        if(app()->environment() == 'testing') JWTAuth::setRequest($request);
    }
}

Mocking JWTAuth

If you would like to avoid the $this->refreshApplication(); after you login, then it is a great idea to mock the login up.

This is how my TestCase looks like now:

<?php

abstract class TestCase extends Laravel\Lumen\Testing\TestCase
{
    /**
     * Creates the application.
     *
     * @return \Laravel\Lumen\Application
     */
    public function createApplication()
    {
        return require __DIR__.'/../bootstrap/app.php';
    }

    protected function headers($user = null)
    {
        $headers = ['Accept' => 'application/json'];

        if (!is_null($user)) {
            $token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user);
            $headers['Authorization'] = 'Bearer '.$token;
        }

        return $headers;
    }
}

Now I can go back to :memory: sqlite again!

Make sure, that you do not mix the “non authorized” and “authorized” requests.

If you need to login and run authorized calls, do it like this:

<?php
public function testUserAndRefreshToken()
{
    $user = factory('App\User')->create();
    $this->get('/api/auth/user', $this->headers(\App\User::first()))
            ->seeJson(['message' => 'authenticated_user']);

    $this->patch('/api/auth/refresh', $this->headers(\App\User::first()))
        ->seeJson(['message' => 'token_refreshed']);
}

Cheers!

I hope I just saved some hours for you.

It was a long day and night.

Buy me a coffee


Share this:

SUBSCRIBE
Subscribe to my e-mail list.
You can unsubscribe anytime.