搜索

首页  >  问答  >  正文

如何在Laravel功能测试中模拟一个使用GuzzleHttp客户端向第三方API发出请求的情况?

在 Laravel 项目(PHP 8.0 上的 Laravel 8)中,我有一个功能测试,其中测试了内部端点。端点有一个控制器调用服务上的方法。然后,服务尝试调用第三方端点。我想模拟的就是这个第三方端点。目前的情况是这样的:

内部端点功能测试

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

内部端点控制器

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

内部服务

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

我看过Guzzle的文档,但似乎MockHandler策略要求您在测试中执行http请求,这不是我在测试中想要的。我希望模拟 Guzzle 的 http 客户端并返回我可以在测试中指定的自定义 http 响应。我尝试像这样模拟 Guzzle 的 http 客户端:

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

但是 InternalService 在测试中似乎没有命中这个模拟。

我也考虑过并尝试使用 Http Fake,但它不起作用,我认为 Guzzle 的 http 客户端没有扩展 Laravel 的 http 客户端。

解决此问题并模拟第三方端点的最佳方法是什么?

编辑

受到 StackOverflow 问题的启发,我通过将带有模拟响应的 Guzzle 客户端注入到我的服务中,成功解决了这个问题。与上述 StackOverflow 问题的区别在于,我必须使用 $this->app->singleton 而不是 $this->app->bind 因为我的 DI 配置不同:

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粉842215006P粉842215006385 天前681

全部回复(1)我来回复

  • P粉617597173

    P粉6175971732023-11-10 09:09:10

    根据您的依赖注入,您希望使用返回模拟响应的自定义 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 控制器内进行单元测试

    回复
    0
  • 取消回复