在 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 配置不同:
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'),
]));
});
}
}
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
根据您的依赖注入,您希望使用返回模拟响应的自定义 Guzzle http 客户端来
bind或singleton化您的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 控制器内进行单元测试