Comment simuler une requête vers une API tierce en utilisant le client GuzzleHttp dans un test fonctionnel Laravel ?
P粉842215006
P粉842215006 2023-11-09 11:42:58
0
1
676

Dans un projet Laravel (Laravel 8 sur PHP 8.0) j'ai un test fonctionnel où les points de terminaison internes sont testés. Le point de terminaison dispose d'un contrôleur qui appelle une méthode sur le service. Le service tente ensuite d'appeler le point de terminaison tiers. C'est ce point de terminaison tiers que je souhaite simuler. La situation actuelle est la suivante :

Tests fonctionnels des points de terminaison internes

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);
}

Contrôleur de point de terminaison interne

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.
    }
}

Service interne

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;
    }
}

J'ai regardé la documentation de Guzzle, mais il semble que MockHandlerLa stratégie vous oblige à effectuer des requêtes http dans vos tests, ce qui n'est pas ce que je souhaite dans mes tests. Je souhaite me moquer du client http de Guzzle et renvoyer une réponse http personnalisée que je peux spécifier dans mon test. J'essaie d'émuler le client http de Guzzle comme ceci :

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);
}

Mais InternalService cette simulation ne semble pas fonctionner lors des tests.

J'ai aussi réfléchi et essayé d'utiliser Http Fake, mais cela n'a pas fonctionné, je pense que le client http de Guzzle n'étend pas le client http de Laravel.

Quelle est la meilleure façon de résoudre ce problème et de se moquer du point de terminaison tiers ?

Modifier

Inspiré par cette question StackOverflow, j'ai réussi à résoudre ce problème en injectant à un client Guzzle une réponse simulée dans mon service. La différence avec la question StackOverflow ci-dessus est que j'ai dû utiliser $this->app->singleton 而不是 $this->app->bind car ma configuration DI était différente :

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

répondre à tous(1)
P粉617597173

En fonction de votre injection de dépendances, vous souhaitez utiliser un client http Guzzle personnalisé qui renvoie une réponse simulée bindsingleton 化您的 InternalService, par exemple comme ceci :

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);
}

Voir aussi : Tests unitaires dans un contrôleur Laravel à l'aide de PHPUnit

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal