intosite's avatar

How to test JWTAuth

Trying to build my API. Trying to build a test for each of the scenarios:

  • no token provided
  • token provided - invalid
  • token provided, user role - user
  • token provided, user role - admin

I can't seem to be able to set any

JWTAuth::shouldRecieve('getToken')->andReturn(true);
0 likes
10 replies
bjones2015's avatar

I don't know exactly how you have your Auth process set up. I have mine as a middleware as follows:

public function handle($request, Closure $next)
    {
        try {
            $user = JWTAuth::parseToken()->authenticate();
            Auth::setUser($user);
        }
        catch (TokenExpiredException $e) {

            return response()->json([
                'error' => 'Token Expired!',
                'statusCode' => (int)401
            ], 401);

        } catch (TokenInvalidException $e) {
            return response()->json([
                'error' => 'Not Authorized!',
                'statusCode' => (int)401
            ], 401);

        } catch (JWTException $e) {
            return response()->json([
                'error' => 'Not Authorized!',
                'statusCode' => (int)401
            ], 401);
        }

        return $next($request);
    }

Then for testing I do something like this:

JWTAuth::shouldReceive('parseToken->authenticate')
            ->andReturn($user);

You can do things similarly for the bad data cases. E.g. change the andReturn to andThrow.

Its been a bit since I set this up, but it seems the trouble I had before was with 'parseToken->authenticate'. Since I wasn't following the public method chain and just had 'parseToken' originally it was not working properly. Like I said though it has been a while so I may be mistaken on that.

Also as a side note, the JWT package actually has a built in middleware you can use, if I am not mistaken. I wanted control over the response so I did not go that route, but it is there if it works for you.

2 likes
intosite's avatar

My problem is when i try to set "shouldRecieve" on JWTAuth, i keep getting this error,

Fatal error: Call to undefined method Tymon\JWTAuth\JWTAuth::shouldReceive()

Should i be importing any class files into my test class?

intosite's avatar

When I try to use a mock user to get a token, i get this error,

ErrorException: Non-static method Tymon\JWTAuth\JWTAuth::fromUser() should not be called statically

What am i doing wrong?

bjones2015's avatar

I'm not having to import JWT into my test files.

Do you have (in app/config.php)

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

in the providers array? And

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

in the aliases array?

Also (someone who is more TDD/BDD knowledgeable feel free to correct me) I don't think you need to mock fromUser. If you mock like I did the the above post if should return the correct user. The only caveat is (I think this is taken care of if you use the middleware provided by the package) is you have to call Auth::setUser($user); after you authenticate. But that is only if you want to use the Auth:: calls later on in the controller.

intosite's avatar

I've imported both the class n providers.

I'm not using any middleware on my APIs, as guest will be able to access different levels of content vs a login user vs admin.

Within my app, I'm able to check tokens, get tokens or get my user info from tokens. But I just can't seem to get any of the phpunit test to pass as long any calls is made to JWT.

intosite's avatar

oh my, i think i found my issue, I was trying to ground my test files in a folder, and that is why it can't find JWTAuth. Something to do with the namespace. If i ran the same code from files created in /tests folder, it's ok. I was using a namespace in my test file removed it, and i stop getting the error "Fatal error: Call to undefined method Tymon\JWTAuth\JWTAuth::shouldReceive()"

So now i can use shouldReceive. But i'm getting a new error, "Fatal error: Call to a member function authenticate() on a non-object"

Another question, how do i test what error pages is being returned? I would like to check my 400,401,403,404,406 etc... Then check the content of the json returned.

bjones2015's avatar
/** @test */
    public function it_should_return_an_error_message_on_a_bad_forgot_token(){
       $this->get($this->getUrl() . '/somebadtoken')
           ->seeJsonContains([
               'error' => 'Token not found',
               'statusCode' => 404
           ])
           ->seeStatusCode(404);
    }

Is an example of a test I use that also looks at the status code. This is assuming you are using the built in testing added to 5.1

As far as your other question its hard so say much since I can't see what you are doing. The error message sounds like you are trying to call the method on null or a primitive type.

Are you calling it like this?

$user = JWTAuth::parseToken()->authenticate();
intosite's avatar

Yes i used $user = JWTAuth::parseToken()->authenticate(); I ended up changing it to

$token =  JWTAuth::parseToken();
$user = JWTAuth:: authenticate();

And set them individually in my test

JWTAuth:: shouldReceive ("parseToken");
JWTAuth:: shouldReceive ("authenticate");

Some how chaining the methods does not work. "parseToken()->authenticate()"

For what's worth, so glad to get my test units all working. First time implementing unit test in a project, and OMG, it's amazing. I don't have to load up PostMan and run like 10 different POST/GET/PUT scenarios every time i tweak a line of code, especially with each having to check on invalid/expired/valid tokens. :D

bjones2015's avatar

That's weird that it works with chaining for me and not you. Regardless I'm glad you got it working!

intosite's avatar

Oh i think one of my problems was that i was importing JWTAuth class and not the facade class in my controller that was throwing errors in my test but works when i ran the controller normally.

Trying to implement repositories now to decouple static methods to my models. Hopefully it'll ease my future test

1 like

Please or to participate in this conversation.