Testing APIs and HTTP Requests Using PHPUnit and Guzzle

Testing APIs and HTTP Requests Using PHPUnit and Guzzle

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())

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:

  1. Creates a MockHandler that simulates an API response.
  2. Defines a fake JSON response for the /users/1 endpoint.
  3. Injects the mocked client into ApiService.
  4. 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.

Leave a Reply