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 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 so we can be more confident in failed states.

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: 11 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.



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here