PHPUnit and mock request from Guzzle

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# Mastering HTTP Testing: Mocking Guzzle Requests with PHPUnit Testing code that interacts with external services, such as making HTTP requests via Guzzle, is a common challenge in software development. When you try to use standard PHPUnit mocking techniques on objects that handle real network operations, you often run into trouble because the system defaults to executing the real logic rather than returning the mocked result. This post addresses the specific issue of mocking Guzzle requests within your class structure and provides a robust, architectural solution. ## The Challenge: Why Direct Mocking Fails You are trying to mock the `request` method on a `GuzzleHttp\Client` instance inside your test. While setting expectations using `$this->method('request')->willReturn(...)` is the correct syntax for mocking, it often fails when dealing with complex HTTP clients like Guzzle for a few reasons: 1. **Instantiation Context:** If your class instantiates the `Client` (`$this->client = new Client;`) internally within the method being tested, PHPUnit's mock setup might not fully intercept the object creation or the subsequent execution flow in a way that prevents actual network calls. 2. **Internal Behavior:** Guzzle is designed to execute real HTTP requests unless specifically configured with a mocking layer or dependency injection. Simply mocking the method call doesn't change how the underlying library executes its logic if it’s not isolated. The key takeaway here is that we need to shift our focus from mocking the *execution* of the request to mocking the *dependency* that provides the request capability. ## The Solution: Dependency Injection and Abstraction The most professional and testable way to handle external service dependencies like Guzzle is through **Dependency Injection (DI)**. Instead of allowing your class to create its own `GuzzleHttp\Client`, you should inject an instance of the client into your class’s constructor. This allows PHPUnit to inject a fully mocked object directly, eliminating the need for complex method mocking on concrete library classes. ### Step 1: Define an Interface (Best Practice) To ensure flexibility and adherence to SOLID principles, define an interface that outlines the contract your class needs from an HTTP client. ```php // src/HttpClientInterface.php interface HttpClientInterface { public function request(string $method, string $uri, array $options): \Psr\Http\Message\ResponseInterface; } ``` ### Step 2: Implement the Client (or use Guzzle directly) Your class now depends on this interface. If you are using Laravel or a similar framework, you would typically inject the actual `GuzzleHttp\Client` implementation, which adheres to your interface contract. ### Step 3: Refactor Your Class for Injection Modify your class to accept the client via the constructor instead of creating it internally. ```php // Example Class Structure class ApiService { private HttpClientInterface $httpClient; public function __construct(HttpClientInterface $httpClient) { $this->httpClient = $httpClient; } public function get(string $uri): stdClass { // Now we use the injected client, not creating a new one $response = $this->httpClient->request( 'GET', $uri, ['headers' => ['Accept' => 'application/json']] ); return json_decode($response->getBody()->getContents()); } } ``` ## Step 4: Mocking the Dependency in PHPUnit With this structure, testing becomes straightforward. You no longer mock Guzzle internals; you mock your custom interface. ```php use PHPUnit\Framework\TestCase; class ApiServiceTest extends TestCase { public function testGetRequestReturns