reaz's avatar
Level 5

Issues with using guzzle in phpunit test.

My app is vue spa backed by Laravel Api. I am using Laravel passport for authentication. in login controller it receives the username and password from the request, and adds the 'passport_client_id' and passport_cleint_secret' to the $request, and uses Guzzle to make a request to the '/oauth/token>' url. This works fine. Then i proceeded to write a test to see if a user can login(i.e: that it receives a access_token). I am getting the following error

GuzzleHttp\Exception\ClientException: Client error: `POST http://localhost:8000/oauth/token` resulted in a `401 Unauthorized` response:
{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}

I have checked if phpunit is missing any data, but it has all the data it needed. Interesting thing is if i make the same request from testclass, it does return a successful response.

My controller:

$http = new \GuzzleHttp\Client();
            $response = $http->post(config('services.passport.login_endpoint'), [
                'form_params' => [
                    'grant_type' => 'password',
                    'client_id' => config('services.passport.client_id'),
                    'client_secret' => config('services.passport.client_secret'),
                    'username' => $request->username,
                    'password' => $request->password,
                ]
            ]);
            return $response->getBody();

My test:

$this->artisan('passport:install');
        $passport_client_secret = DB::table('oauth_clients')->where('id',2)->value('secret');
        Config::set('services.passport.client_id', 2);
        Config::set('services.passport.client_secret', $passport_client_secret);
        
        $user = factory('App\User')->create();

        $response = $this->json('post','api/v1/login',[
            'username' => $user->email,
            'password' => 'password'
        ]);

For debugging , i tried directly making the api request from testclass using the following code , it succeeds.

$response = $this->json('post',config('services.passport.login_endpoint'), [
                'grant_type' => 'password',
                'client_id' => config('services.passport.client_id'),
                'client_secret' => config('services.passport.client_secret'),
                'username' => $user->email,
                'password' => 'password',
        ]);

Any idea why guzzle behaves different when used in PHPunit? What can i do to successfully test this controller? Thanks in advance.

0 likes
3 replies
vstruhar's avatar

I just had the same issue and after a few hours managed to find a solution.

The thing is that when you are making another request from the controller, the second request is accessing Laravel app that is not in the testing environment. This means that the Laravel app is probably in dev env and using your dev database which has the client with a different secret, that's why they don't match and you get invalid_client error in response.

What I did was send a custom header that will determine if the app needs to use testing database:

In Controller:

Http::withHeaders(['x-testing' => app()->environment('testing')])
     ->post('/oauth/token', [...]);

In AppServiceProvider added this to register method:

if (request()->header('x-testing') == 1) {
      config()->set('database.connections.mysql.database', env('DB_DATABASE_TESTING'));
}

PS: also don't forget to set the DB_DATABASE_TESTING in the .env.

Not the best solution but it works, let me know if somebody has a better solution :)

2 likes
dimaaash's avatar

Hi,

I think i am facing the same issue. I am trying to test out an API's auth endpoint (oauth). So the app is set up for the testing. On each run migrations are run, db seeded and passport is installed.

However when trying to hit the auth endpoint it seems to throw:

{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}

Could you elaborate a little more on the Controller part:

Http::withHeaders(['x-testing' => app()->environment('testing')])
     ->post('/oauth/token', [...]);

Not quite sure i understood that part.

Thank you for your time.

reaz's avatar
Level 5

The easiest solution i have found to this problem is, make a.env.testing file with all test db and other config. Then before you run the tests, run the following command to

php artisan config:cache --env=testing

Then after you have finished running tests, go back to your normal env

php artisan config:cache

Please or to participate in this conversation.