Defense Programming: Anticipating Failures with Tests

Published on by

Defense Programming: Anticipating Failures with Tests image

When you start working on a new feature, it is wise to plan out not only how it is expected to work, but what happens if something fails. Taking the time up front to anticipate failure is a quality of a great developer.

As a simple example, consider a blog that is populated by data from a third-party service. For instance, the home page of Laravel News pulls in jobs from LaraJobs. What happens if LaraJobs goes down? Or stops working?

Since we don’t know when a dependency might fail, it’s best to plan for failure by having tests so we can be more confident in failed states.

Laravel can help us write tests that plan for failure using real-time facades, but before we jump in, let’s create a fictional implementation for pulling in a list of articles to our site.

Let’s start with the following example of an ArticleRepository class with a Guzzle HTTP client as a dependency:

<?php
 
namespace AppRepositories;
 
use GuzzleHttpClient;
 
 
class ApiArticleRepository implements ArticleRepository
{
public function __construct(Client $client)
{
$this->client = $client;
}
 
public function get($id)
{
return $this->client->get('posts', ['query' => ['id' => $id]]);
}
}

If you’re not familiar with Guzzle:

Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.

One approach we can take in our ApiArticleRepository class is to hide this implementation behind an interface that you can use to instantiate this class using Laravel’s service container:

<?php
 
namespace AppContracts;
 
interface ArticleRepository
{
public function get();
}

Now, let’s bind the implementation to the interface so the container can resolve this class every time we try to instantiate the interface using dependency injection in our controllers.

Binding an interface to our concrete implementation can be done in the AppProvidersAppServiceProvider class:

<?php
 
use GuzzleHttp/Client;
 
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('PostRepository', function () {
return new ApiPostRepository(new GuzzleClient ([
'base_uri' => config('api.url')
]);
});
}

And here’s an example of how you implement the repository in a controller:

<?php
 
namespace AppHttpControllers;
 
use AppRepositoriesApiArticleRepository as Repository;
 
class RegisterController extends Controller
{
protected $repository;
 
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
 
public function view($id)
{
return view('artice.view', ['article' => $this->repository->get($id)]);
}
}

Testing Failure

With our ApiArticleRepository class in mind, we can start by thinking what would happen if the API responded with an exception instead.

What will happen when Guzzle tries to make a request and it fails?

Laravel has a concept called real-time facades that we can use to mock an exception and test how our code responds.

Using Real-time Facades

If you are unfamiliar with real-time facades, they can be defined as the following:

Facades provide a “static” interface to classes that are available in the application’s service container.

One of the advantages of working with facades is the fact you have access to a couple of methods that help you to create mocks of any class within your test environment.

How to instantiate a class using real-time facades?

The only thing you need to do is add the Facades prefix to the use statement like so:

<?php
 
use FacadesGuzzleHttpClient as GuzzleClient;

In this case, instead of mocking our ApiArticleRepository::class, we can go one layer back, and mock the GuzzleHttpClient::class instead. We can force each method of this class to return the desired response, and by doing so, we don’t need to change any implementation of the repository class.

The first step would be updating the ApiArticleRepository class to use a Facade instead dependency injection.

<?php
 
namespace AppRepositories;
 
use FacadesGuzzleHttpClient;
 
 
class ApiArticleRepository implements ArticleRepository
{
 
public function get($id)
{
return Client::get('posts', ['query' => ['id' => $id]]);
}
}

Mock a Guzzle Response

<?php
 
class ClientTest extends TestCase
/**
* @test
*/
public function testing_guzzle_exception()
{
FacadesGuzzleHttpClient::shouldReceive('get')->andThrow(
new GuzzleHttpExceptionRequestException(
"Error Communicating with Server",
new GuzzleHttpPsr7Request('GET', 'test')
)
);
 
$this->expectException(GuzzleHttpExceptionRequestException::class);
 
$repository = resolve('PostRepository');
$response = $repository->where(['limit' => 1]);
}
}

The results of this test:

$ phpunit --filter=testing_guzzle_exception
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.
 
. 1 / 1 (100%)
 
Time: 131 ms, Memory: 12.00MB
 
OK (1 test, 2 assertions)

Using the shouldReceive() method returns an instance of MockeryExpectation::class, so we can chain the method andThrown() to specify which exception is thrown each time the app tries to run the method get() on the GuzzleHttpClient instance.

The following line is an assertion itself, and it would return an error in our tests if the expected exception never gets fired

$this->expectException(GuzzleHttpExceptionRequestException::class);

When the ApiArticleRepository::get() try to access to the GuzzleHttpClient::get() method, instead of a successful response, the specified exception is going to be thrown.

With this approach, you’ll be able to do higher level tests like:

<?php
 
class ClientTest extends TestCase
/**
* @test
*/
public function testing_guzzle_exception()
{
FacadesGuzzleHttpClient::shouldReceive('get')->andThrow(
new GuzzleHttpExceptionRequestException(
"Error Communicating with Server",
new GuzzleHttpPsr7Request('GET', 'test')
)
);
 
$this->expectException(GuzzleHttpExceptionRequestException::class);
 
$response = $this->get('/');
$response->assertStatus(500);
}
}

In this case, our tests are going to pass as well.

$ phpunit --filter=testing_guzzle_exception
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.
 
. 1 / 1 (100%)
 
Time: 131 ms, Memory: 12.00MB
 
OK (1 test, 2 assertions)

Final thoughts

I think this is a relatively easy approach to test your application and anticipate failure when you need to use external services and third-party APIs with guzzle.

Also, if you are just starting to work with tests, believe me, you are going to find this so much easy rather than trying to create mocks, stubs, using doubles, etc.

That’s all, now you are ready to write an HTTP client implementation backed by tests that responds to external API failure.

Jeff photo

I'm a full-stack web developer and a part-time writer.

You can find more of my writing on https://medium.com/@jeffochoa.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Visit Paragraph
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
LoadForge logo

LoadForge

Easy, affordable load testing and stress tests for websites, APIs and databases.

LoadForge
Paragraph logo

Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Paragraph
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
Bacancy - Staff Augmentation logo

Bacancy - Staff Augmentation

Leave your web app development hustles to the leading IT Staff Augmentation Service Providers. Choose from an extensive pool of 1050+ developers and give yourself the sigh of success you deserve with Bacancy. Get In Touch Today!

Bacancy - Staff Augmentation
DocuWriter.ai logo

DocuWriter.ai

Save hours of manually writing Code Documentation, Comments & DocBlocks, Test suites and Refactoring.

DocuWriter.ai
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Add Kanban Boards to Your Laravel App in Seconds image

Add Kanban Boards to Your Laravel App in Seconds

Read article
October CMS v3.6 Ships Today, Full of New Features image

October CMS v3.6 Ships Today, Full of New Features

Read article
Laracon EU Videos are now out image

Laracon EU Videos are now out

Read article
Use Google's Gemini AI in Laravel image

Use Google's Gemini AI in Laravel

Read article
PhpStorm is getting a brand new terminal image

PhpStorm is getting a brand new terminal

Read article
Six Essential Plugins for Visual Studio Code image

Six Essential Plugins for Visual Studio Code

Read article