在 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'), ])); }); } }
P粉6175971732023-11-10 09:09:10
根據您的依賴注入,您希望使用返回模擬回應的自訂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 控制器內進行單元測試