Defense Programming: Anticipating Failures with Tests
Published on by Jeff
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_exceptionPHPUnit 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_exceptionPHPUnit 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.
I'm a full-stack web developer and a part-time writer.
You can find more of my writing on https://medium.com/@jeffochoa.