Learn how to test APIs and HTTP requests using PHPUnit and Guzzle. Verify API responses, handle errors, and mock HTTP requests for reliable testing.
Introduction
Modern PHP applications often interact with third-party APIs, microservices, and web services. Ensuring that these API calls work correctly is critical for building reliable applications.
PHPUnit combined with Guzzle, a popular HTTP client for PHP, allows us to:
- Send HTTP requests and verify API responses
- Test API endpoints with different request methods (GET, POST, PUT, DELETE)
- Mock API responses to avoid real network calls
- Handle errors and exceptions in API interactions
This guide covers:
- Setting up PHPUnit with Guzzle for API testing
- Writing tests for different HTTP methods
- Using Guzzle to send requests and validate responses
- Mocking API responses for unit testing
1. Installing PHPUnit and Guzzle
Before testing API requests, install PHPUnit and Guzzle via Composer.
composer require --dev phpunit/phpunit guzzlehttp/guzzle
This installs both PHPUnit for unit testing and Guzzle for making HTTP requests.
2. Making API Requests in PHPUnit Using Guzzle
Example API Client Class
Create a class that interacts with an API using Guzzle.
<?php
namespace App;
use GuzzleHttp\Client;
class ApiService
{
protected $client;
public function __construct()
{
$this->client = new Client(['base_uri' => 'https://jsonplaceholder.typicode.com']);
}
public function getUser($userId)
{
$response = $this->client->get("/users/{$userId}");
return json_decode($response->getBody(), true);
}
}
This class:
- Initializes a Guzzle HTTP client with a base API URL.
- Defines a method
getUser($userId)
that sends a GET request and returns JSON data.
3. Writing PHPUnit Tests for API Requests
Testing the API Client with Real API Calls
use PHPUnit\Framework\TestCase;
use App\ApiService;
class ApiServiceTest extends TestCase
{
public function testGetUserReturnsValidResponse()
{
$apiService = new ApiService();
$user = $apiService->getUser(1);
$this->assertIsArray($user);
$this->assertArrayHasKey('id', $user);
$this->assertEquals(1, $user['id']);
}
}
How This Works:
- Calls the
getUser(1)
method, which fetches data from an external API. - Uses PHPUnit assertions to check if the response:
- Is an array (
assertIsArray()
) - Contains an "id" key (
assertArrayHasKey()
) - Has the correct user ID (
assertEquals()
)
- Is an array (
This test confirms that the API returns a valid user response.
4. Mocking API Requests Using Guzzle Mock Handler
Why Use Mocking for API Testing?
- Avoids hitting real API endpoints, preventing network issues.
- Speeds up tests by eliminating HTTP requests.
- Allows testing error scenarios, like timeouts or invalid responses.
Setting Up a Mock Guzzle Client
Modify the ApiService
class to allow dependency injection for easier testing.
<?php
namespace App;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
class ApiService
{
protected $client;
public function __construct(ClientInterface $client = null)
{
$this->client = $client ?: new Client(['base_uri' => 'https://jsonplaceholder.typicode.com']);
}
public function getUser($userId)
{
$response = $this->client->get("/users/{$userId}");
return json_decode($response->getBody(), true);
}
}
Creating a PHPUnit Test with a Mocked API Response
use PHPUnit\Framework\TestCase;
use App\ApiService;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
class ApiServiceTest extends TestCase
{
public function testGetUserReturnsMockedResponse()
{
// Define a fake API response
$mock = new MockHandler([
new Response(200, [], json_encode(['id' => 1, 'name' => 'John Doe']))
]);
$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);
// Inject mocked client into ApiService
$apiService = new ApiService($client);
$user = $apiService->getUser(1);
// Assertions
$this->assertIsArray($user);
$this->assertEquals(1, $user['id']);
$this->assertEquals('John Doe', $user['name']);
}
}
How This Works:
- Creates a MockHandler that simulates an API response.
- Defines a fake JSON response for the
/users/1
endpoint. - Injects the mocked client into
ApiService
. - Runs assertions to verify that the response matches expectations.
5. Testing API Errors and Exceptions
Handling API Errors in the ApiService Class
Modify getUser()
to handle API errors gracefully.
public function getUser($userId)
{
try {
$response = $this->client->get("/users/{$userId}");
return json_decode($response->getBody(), true);
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
}
Testing API Error Handling
Simulating an HTTP 404 error response for a non-existent user.
public function testGetUserHandlesNotFoundError()
{
$mock = new MockHandler([
new Response(404, [], json_encode(['error' => 'User not found']))
]);
$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);
$apiService = new ApiService($client);
$response = $apiService->getUser(999);
$this->assertArrayHasKey('error', $response);
$this->assertEquals('User not found', $response['error']);
}
Why Test Error Scenarios?
- Ensures API failures are handled correctly.
- Prevents unexpected crashes in real applications.
6. Best Practices for Testing APIs in PHPUnit
- Use Mocking Instead of Real API Calls – Reduces test execution time and avoids dependency on network availability.
- Validate Response Structure – Ensure API responses contain required fields before using them.
- Test Different HTTP Methods – Cover GET, POST, PUT, DELETE requests where applicable.
- Test Error Handling and Timeouts – Simulate API failures and check application behavior.
- Use Dependency Injection for Guzzle Clients – Allows easier mocking and better test flexibility.
Conclusion
Unit testing API requests in PHP using PHPUnit and Guzzle ensures that your application communicates correctly with external services.
This guide covered:
- Writing API tests with Guzzle and PHPUnit
- Mocking API requests using Guzzle’s MockHandler
- Handling and testing API errors and exceptions
- Best practices for maintainable API tests
By implementing these techniques, you can ensure API interactions are stable, predictable, and resilient to failures.