Wie simuliere ich eine Anfrage an eine Drittanbieter-API mithilfe des GuzzleHttp-Clients in einem Laravel-Funktionstest?
P粉842215006
P粉842215006 2023-11-09 11:42:58
0
1
657

In einem Laravel-Projekt (Laravel 8 auf PHP 8.0) habe ich einen Funktionstest, bei dem die internen Endpunkte getestet werden. Der Endpunkt verfügt über einen Controller, der eine Methode für den Dienst aufruft. Der Dienst versucht dann, den Endpunkt des Drittanbieters anzurufen. Es ist dieser Endpunkt eines Drittanbieters, den ich simulieren möchte. Die aktuelle Situation ist wie folgt:

Interne Endpunkt-Funktionstests

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

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

Inhouse-Service

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

Ich habe mir die Dokumentation von Guzzle angesehen, aber es scheint, dass MockHandlerDie Strategie erfordert, dass Sie in Ihren Tests http-Anfragen ausführen, was in meinen Tests nicht das ist, was ich möchte. Ich möchte den http-Client von Guzzle verspotten und eine benutzerdefinierte http-Antwort zurückgeben, die ich in meinem Test angeben kann. Ich versuche, den http-Client von Guzzle wie folgt zu emulieren:

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

Aber InternalService diese Simulation scheint beim Testen nicht zu überzeugen.

Ich habe auch über die Verwendung von Http Fake nachgedacht und es versucht, aber es hat nicht funktioniert. Ich denke, der http-Client von Guzzle erweitert den http-Client von Laravel nicht.

Was ist der beste Weg, dieses Problem zu lösen und den Endpunkt eines Drittanbieters zu verspotten?

Bearbeiten

Inspiriert durch diese StackOverflow-Frage habe ich dieses Problem erfolgreich gelöst, indem ich einen Guzzle-Client mit einer simulierten Antwort in meinen Dienst eingefügt habe. Der Unterschied zur obigen StackOverflow-Frage besteht darin, dass ich $this->app->singleton 而不是 $this->app->bind verwenden musste, weil meine DI-Konfiguration anders war:

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

Antworte allen(1)
P粉617597173

根据您的依赖注入,您希望使用返回模拟响应的自定义 Guzzle http 客户端来bindsingleton 化您的 InternalService ,例如像这样:

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

另请参阅:使用 PHPUnit 在 Laravel 控制器内进行单元测试

Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage