0

I have a login function that needs to be called from a separate User Service API.

The sole purpose of logging in is to be used on testing, because I need to get the bearer token that will be used as the parameter for one of my middleware.

As for the testing, is it possible to call external api thru HTTP Request only once? If so, where should I put it?

I tried it on the setUp() function but it seems to be called every time a test function is executed on the test class, making the test slow.

EDITED with Code:

The test code:

<?php

namespace Tests\Feature\Controllers;

use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\TestSuite;
use Tests\TestCase;

class MyTest extends TestCase
{
    protected string $bearerToken;

    public function setUp(): void
    {
        parent::setUp();

        $this->bearerToken = self::getToken();
    }

    protected static function getToken()
    {
        $response = Http::post('http://auth_api/oauth/token', [
            ... 
            ...
        ]);
        
        // but assume that this request always succeed.
        if ($response->failed()) return [];

        return json_decode(json_encode($response->json()), true)['access_token'];
    }

    ...test methods here
}

I also tried doing manual flagging, so that the custom login function will only be fetched once throughout the whole test suite.

like below:

protected static $isInitiated = false;
protected string $bearerToken;

public function setUp(): void
{
    parent::setUp();

    if (! self::$isInitiated) {
        $this->bearerToken = self::getToken();

        self::$isInitiated = true;
    }
}

Based on the answer here

but it gives me error saying:

$bearerToken must not be accessed before initialization

So, from that error, the test methods must've been executed first before it even gave value to $bearerToken.

I also tried public static function setUpBeforeClass():

protected static ?string $bearerToken = null;

public static function setUpBeforeClass(): void
{
    self::$bearerToken = self::getToken();
}

But it also gives me error saying:

A facade root has not been set.

Is there any way to do this?

nwantiti
  • 41
  • 4
  • `setUp` is executed every time you run a test (a method)... You need to use `$this->actingAs($user)`, or depending on what package you are using, but it is similar... we need more info... – matiaslauriti Feb 01 '23 at 04:28
  • As I've said, I don't have a user model in this codebase, I'm calling it from a separate service api – nwantiti Feb 01 '23 at 04:35
  • You need to show some code, we can't help if we can't see... – matiaslauriti Feb 01 '23 at 05:24
  • @matiaslauriti Done. edited with codes in it. – nwantiti Feb 01 '23 at 05:52
  • We need to see how you are testing... We need to see a full test, so we can tell you if you need to mock stuff, where, etc. I am assuming your test is not an integration test but a feature test, so in that case you should not be calling an external API. You have to mock the calls to the service – matiaslauriti Feb 01 '23 at 06:08
  • Well, I have a middleware that verifies if that bearer token is valid, and if it is, I am calling `/me` again, and inject the user data into the request. I can't just test without that middleware. – nwantiti Feb 01 '23 at 06:30
  • what do you do to verify if it is valid? you have to fake/mock that – matiaslauriti Feb 01 '23 at 06:35

1 Answers1

0

You shouldn't call any external api in testing, the reason why is simple, You are testing the app you or your team coded, not someone else.

So, you should mocking all external parts like use Http::fake().

Http::fake([
    // Stub a JSON response for GitHub endpoints...
    'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),
 
    // Stub a string response for Google endpoints...
    'google.com/*' => Http::response('Hello World', 200, $headers),
]);

Here is document: https://laravel.com/docs/9.x/http-client#faking-responses

And if you really want to call external api, new a GuzzleClient, then you can do what you want, but you should realize that is not a good idea.

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
    'auth' => ['user', 'pass']
]);
echo $res->getStatusCode();
// "200"
echo $res->getHeader('content-type')[0];
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
Charlie
  • 287
  • 11
  • But then, if I don't call my Auth Service API how would I proceed with my controller's function, since I need to inject the User's data(this user data is being `required` in the request validation), coming from that bearer token that I needed so badly to fetch in the test class. – nwantiti Feb 01 '23 at 06:50
  • Normally, I wouldn't have to perform a login function, the bearer token is simply passed as a header to the request, but I can't just fake a bearer token during testing. – nwantiti Feb 01 '23 at 06:51
  • Just pass fake user data that you expected external api return into your app. If user data not in your app then auth middleware should be always skipped in testing because the auth action doesn't happed on your app. – Charlie Feb 01 '23 at 07:59
  • If it does have user model in your app and you want to simulate user action, you can use "actingAs()" method, it will directly create specific user session as same as normal user logged. document: https://laravel.com/docs/9.x/passport#testing. – Charlie Feb 01 '23 at 08:13
  • Method actingAs can pass User model and guard to simulate that user passed your auth. Example: $this->actingAs(User::find(1), 'auth'). Notice that 'auth' is auth guard name. After you acting user, you can just access logged user by helper or facade like auth()->user(). – Charlie Feb 01 '23 at 08:22
  • Yep, all of these, I considered calling external API without mocking is really a bad idea. I ended up just refactoring the middleware, to inject dummy user data on testing environment. Thanks all. – nwantiti Feb 01 '23 at 08:32