Summer Sale! All accounts are 50% off this week.

pdigital's avatar

Http Client is not resetting instance using swap()

I'm writing a feature test in which I need to fake multiple requests.

Currently, when I run the test, only the first request is actually faked, any other request not.

After some searching, I came across this Playground and found out that one can use the swap() method to reset the fake instance: https://laravelplayground.com/#/snippets/ffb7f281-5345-4a26-b94f-7e5cb8a58514

I tried this out, but it doesn't seem so work (Laravel 9)? I copied the code in my test, and when I dump the response, it still dumps the first response twice.

Any help here?

Code:

Http::fake([
    'https://login.bol.com/token' => Http::response(json_encode($response)),
]);

$fake = app(\Illuminate\Http\Client\Factory::class);
Http::swap($fake);

foreach ($this->orders() as $order) {
    Http::fake([
        "https://api.bol.com/retailer-demo/orders/{$order}" => Http::response(['foo' => 'bar']),
    ]);

    $this->client->getOrder($user, $order); // $this->client does a Http request
}

Code I'm trying to test:

class Client extends BaseClient
{
    public function getOrder(User $user, string $orderId): ?array
    {
        $this->authenticate($user);

        $this->get($orderId); // Method in the BaseClient which does a request using HttpClient
    }

    protected function authenticate(User $user): void
    {
        $this->setAccessToken($user);
    }
}

class BaseClient
{
    protected AccessToken $accessToken;

    protected function setAccessToken(User $user): void
    {
        $accessToken = $this->getAccessToken($user);

        if (empty($this->accessToken)) {
            $this->accessToken = $accessToken;
            return;
        }

        if ($this->accessToken !== $accessToken) {
            $this->accessToken = $accessToken;
        }
    }

    protected function getAccessToken(User $user): ?AccessToken
    {
        try {
            $credentials = $user->getCredentials();
            $accessToken = $credentials->accessToken;

            if (null === $accessToken) {
                return $this->requestAccessToken($credentials);
            }

            if (now() > $accessToken->expires_at) {
                $accessToken->delete();
                return $this->requestAccessToken($credentials);
            }

            return $accessToken;
        } catch (\Exception $e) {
            report($e);

            return null;
        }
    }

    private function requestAccessToken(Credential $credential): ?AccessToken
    {
        try {
            $authClient = new AuthClient(
                credential: $credential
            );

            $authClient->authenticate();
            $token = $authClient->getToken();

            return $credential->accessToken()->create([
                'access_token' => $token['access_token'],
                'expires_at' => $token['expires_at'],
            ]);
        } catch (\Exception $e) {
            report($e);

            return null;
        }
    }
}

Ultimately, I'm trying to assert that only one AccessToken is requested, created and used, as long as it's not expired.

0 likes
2 replies
ramonrietdijk's avatar
Level 30

When using Http::fake Laravel already uses the swap method behind the scenes. I would recommend just having one Http::fake for all of your requests. Note that you can also use wildcards.

Http::fake([
    'https://login.bol.com/token' => Http::response(json_encode($response)),
    'https://api.bol.com/retailer-demo/orders/*' => Http::response(['foo' => 'bar']),
]);

If you specifically wish to check if certain requests are (not) sent, you may be interested in using the assertSent and assertNotSent methods.

Please or to participate in this conversation.