Day 1: lumen-jwt - testing tymon's JWTAuth
The Quest
- Sync Electron+Vue.js ToDo App with Lumen Part 1 (2017-01-21)
- Lumen TDD: Testing a ToDo App (2017-01-15)
- Day 1: lumen-jwt - testing tymon's JWTAuth (2017-01-09)
- The Quest (2017-01-08)
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