DEV Community

jcfrane
jcfrane

Posted on

Testing / Mocking Laravel HTTP Client Response / Request

In my recent coding endeavors, I unexpectedly found myself immersed in the world of writing tests—a departure from my usual programming routine. The particular challenge that caught me off guard was the intricacy of mocking objects, especially when dealing with Laravel Http Client requests and responses.
Let me walk you through a recent scenario. I've been working on a BlockchainService class with a critical function: getConfirmations(string $hash): int. This function interacts with other microservices by making HTTP requests and handling responses.
Here's a snippet of the function's definition:

public function getConfirmations(string $hash, array $context = []): int
    {
        try {
            $token = $this->authServiceV2->getClientCredentialsToken();
            $response = $this->request
                ->throw()
                ->get('/api/internal/v2/blockchain/confirmations/' .  $hash);

            $json = $response->json();
            $json = [];

            return $json['confirmations'];
        } catch (RequestException $exception) {
            throw new InternalCallException($exception);
        }
    }

Enter fullscreen mode Exit fullscreen mode

Now, the challenges I faced were multi-faceted:

1. Mocking Illuminate\Http\Client\PendingRequest:

I crafted a utility function in my TestCase class to create a mock object for PendingRequest. This involved setting up the expected behavior for the get method and creating a mock response using Guzzle.

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response as LaravelResponse;
use PHPUnit\Framework\MockObject\MockObject;

abstract class TestCase extends BaseTestCase
{
  protected \Exception $exceptionToThrow;

  public function getHttpResponseMock($status, $headers = [], $body = [])
  {
      $mock = \Mockery::mock(PendingRequest::class)->makePartial();

      $laravelResponse = $this->createLaravelHttpResponse($status, $headers, $body);

      $mock->shouldReceive('withMiddleware')
          ->andReturn($mock);

      $mock->shouldReceive('get')
          ->andReturnUsing(function () use ($laravelResponse) {
              if (isset($this->exceptionToThrow)) {
                  throw $this->exceptionToThrow;
              }
              return $laravelResponse;
          })
          ->once();

      return $mock;
  }


  public function createLaravelHttpResponse($status, $headers = [], $body = []): LaravelResponse
  {
      $guzzleResponse = new \GuzzleHttp\Psr7\Response($status, $headers, json_encode($body));

      return new LaravelResponse($guzzleResponse);
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Mocking AuthServiceV2:

To mock the AuthServiceV2, I created a partial mock with expectations for the getClientCredentialsToken method.

use App\Services\AuthServiceV2;
use PHPUnit\Framework\MockObject\MockObject;

public function createAuthServiceV2PartialMock(): MockObject
{
    $authService = $this->createMock(AuthServiceV2::class);
    $authService
        ->expects($this->once())
        ->method('getClientCredentialsToken')
        ->willReturn('mocked_token');

    return $authService;
}
Enter fullscreen mode Exit fullscreen mode

3. Making Assertions on the Function:

Bringing it all together in a test method, I validated that the getConfirmations function behaves as expected by asserting the result against the expected confirmations.

public function it_can_get_confirmations()
{
    $expectedConfirmations = 10;
    $hash = 'example_hash';

    $pendingRequest = $this->getHttpResponseMock(200, [], [ 'confirmations' => $expectedConfirmations ]);
    $authService = $this->createAuthServiceV2PartialMock();

    // Create instance
    $blockchainService = new BlockchainService($pendingRequest, $authService);

    // Act
    $confirmations = $blockchainService->getConfirmations($hash);

    // Assert
    $this->assertEquals($expectedConfirmations, $confirmations);

Enter fullscreen mode Exit fullscreen mode

This cohesive approach ensured a comprehensive testing environment, covering the intricacies of HTTP client mocking and service authentication. The utilization of mock objects and thoughtful assertions allowed for robust validation of the getConfirmations function, providing confidence in its functionality.

In the ever-evolving landscape of software development, navigating testing complexities becomes an integral part of ensuring the reliability and robustness of our code. Embracing challenges and employing effective testing strategies are key steps toward code excellence and maintainability. Here's to conquering the testing terrain! 🚀✨

Originally posted at: https://jcfrane.com

Top comments (0)