Create Mocks for API Clients in Laravel
This article has been published a while ago.
If this is a technical article some information might be out of date. If something is terribly broken, let me know and I will update the article accordingly.
When I work with APIs, I usually create a single PHP class which is responsible for sending the Guzzle HTTP request. It usually looks like this:
namespace App\Services\IMDB;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
class HttpClient
{
public function send(array $payload) : Response
{
return app(Client::class)
->request('POST', 'https://api.example.com/movies', $payload);
}
}
I don't write Unit Tests for such a simple class, but I will write Integration Tests to cover the feature, which will trigger the class in some way or another.
When I want to test how my app reacts to different responses or just to make sure the HttpClient always returns the same response, I mock the HttpClient.
Laravel makes this really easy with the $this->mock()
helper (Thanks Taylor).
use GuzzleHttp\Psr7\Response;
use App\Services\IMDB\HttpClient;
use Illuminate\Support\Facades\File;
$mock = $this->mock(HttpClient::class);
$mock->shouldReceive('send')
->andReturn(new Response(
$status = 200,
$headers = [],
File::get(base_path('tests/stubs/success.json'))
));
This code snippet will create a new Mock for HttpClient
and will instruct the Mock to return an instance of GuzzleHttp\Psr7\Response
when send
is called. The content of success.json
is an example response I've once received from the real API.
My Integration Test will then look similar to this:
use GuzzleHttp\Psr7\Response;
use App\Services\IMDB\HttpClient;
use Illuminate\Support\Facades\File;
/** @test */
public function it_updates_a_movie_with_data_from_imdb()
{
// success.json
// {
// "title": "Skyfall",
// "release_date": "2012-10-27",
// "cover": "https://example.org/movies/skyfall/cover.jpg"
// }
$mock = $this->mock(HttpClient::class);
$mock->shouldReceive('send')
->andReturn(new Response(
$status = 200,
$headers = [],
File::get(base_path('tests/stubs/imdb-responses/success.json'))
));
$response = $this->post('/movies', [
'title' => 'Skyfall'
]);
$this->assertDatabaseHas('movies', [
'title' => 'Skyfall',
'release_date' => '2012-10-27'
]);
}
I use this all the time, but always forget the details and have to search in old projects for code snippets. 🤷