How to simulate a request to a third-party API using the GuzzleHttp client in a Laravel functional test?
P粉842215006
P粉842215006 2023-11-09 11:42:58
0
1
513

In a Laravel project (Laravel 8 on PHP 8.0), I have a functional test in which an internal endpoint is tested. The endpoint has a controller that calls a method on the service. The service then attempts to call the third-party endpoint. It is this third party endpoint that I want to simulate. The current situation is as follows:

Internal endpoint functional testing

public function testStoreInternalEndpointSuccessful(): void { // arrange, params & headers are not important in this problem $params = []; $headers = []; // act $response = $this->json('POST', '/v1/internal-endpoint', $params, $headers); // assert $response->assertResponseStatus(Response::HTTP_OK); }

Internal Endpoint Controller

class InternalEndpointController extends Controller { public function __construct(protected InternalService $internalService) { } public function store(Request $request): InternalResource { $data = $this.internalService->fetchExternalData(); return new InternalResource($data); // etc. } }

Internal Services

use GuzzleHttpClientInterface; class InternalService { public function __construct(protected ClientInterface $client) { } public function fetchExternalData() { $response = $this->httpClient->request('GET', 'v1/external-data'); $body = json_decode($response->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR); return $body; } }

I've looked at Guzzle's documentation, but it seems that theMockHandlerstrategy requires you to perform http requests in your tests, which is not what I want in my tests. I wish to mock Guzzle's http client and return a custom http response that I can specify in my test. I try to emulate Guzzle's http client like this:

public function testStoreInternalEndpointSuccessful(): void { // arrange, params & headers are not important in this problem $params = []; $headers = []; $mock = new MockHandler([ new GuzzleResponse(200, [], $contactResponse), ]); $handlerStack = HandlerStack::create($mock); $client = new Client(['handler' => $handlerStack]); $mock = Mockery::mock(Client::class); $mock ->shouldReceive('create') ->andReturn($client); // act $response = $this->json('POST', '/v1/internal-endpoint', $params, $headers); // assert $response->assertResponseStatus(Response::HTTP_OK); }

ButInternalServicedoesn't seem to hit this mock in testing.

I also thought about and tried using Http Fake, but it didn't work, I think Guzzle's http client doesn't extend Laravel's http client.

What is the best way to solve this problem and mock the third party endpoint?

edit

Inspired by this StackOverflow question, I successfully solved this problem by injecting a Guzzle client with mocked responses into my service. The difference from the above StackOverflow question is that I had to use$this->app->singletoninstead of$this->app->bindbecause of my DI configuration different:

AppServiceProvider.php

namespace AppProviders; use AppServiceInternalService; use GuzzleHttpClient; use IlluminateSupportServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { // my app uses ->singleton instead of ->bind $this->app->singleton(InternalService::class, function () { return new InternalService(new Client([ 'base_uri' => config('app.internal.base_url'), ])); }); } }


P粉842215006
P粉842215006

reply all (1)
P粉617597173

Depending on your dependency injection, you want tobindorsingletonyourInternalServicewith a custom Guzzle http client that returns a mocked response, e.g. like this:

public function testStoreInternalEndpointSuccessful(): void { // depending on your DI configuration, // this could be ->bind or ->singleton $this->app->singleton(InternalService::class, function($app) { $mockResponse = json_encode([ 'data' => [ 'id' => 0, 'name' => 'Jane Doe', 'type' => 'External', 'description' => 'Etc. you know the drill', ] ]); $mock = new GuzzleHttp\Handler\MockHandler([ new GuzzleHttp\Psr7\Response(200, [], $mockResponse), ]); $handlerStack = GuzzleHttp\HandlerStack::create($mock); $client = new GuzzleHttp\Client(['handler' => $handlerStack]); return new InternalService($client); }); // arrange, params & headers are not important in this problem $params = []; $headers = []; // act $response = $this->json('POST', '/v1/internal-endpoint', $params, $headers); // assert $response->assertResponseStatus(Response::HTTP_OK); }

See also:Unit testing inside a Laravel controller using PHPUnit

    Latest Downloads
    More>
    Web Effects
    Website Source Code
    Website Materials
    Front End Template
    About us Disclaimer Sitemap
    php.cn:Public welfare online PHP training,Help PHP learners grow quickly!