tzurbaev's avatar

How Do You Test Remote APIs in Application Testing?

Let's say you're building an application that in some way interacts with remote APIs (payment services, for example). You've mocked API providers in Unit tests and not making real requests, but what would you do when it comes to application testing?

I have an API (my app's) route that collects some inputs and then sends them to remote API (not my app's). Since I'm using the same code to execute requests both from controllers and tests, I can trust unit testing results, skip application testing and assume that everything works fine.

But what if something gets broken in the controller code, for example, inputs validation? Of course, I need to run application tests as well. But how should I bypass real API calls?

Should I create a dummy class for API provider and add query string parameter/header (in application tests) to instruct my application to swap this dummy with original API provider? If so, what if I have multiple places where this API provider is called from? Check query/header everywhere? Or should I check them directly in API Provider and make my classes more tight-coupled with external dependenices (I would rather not to do so)?

Maybe someone have more correct/elegant way to test remote APIs in application testing?

0 likes
2 replies
luceos's avatar

Hi @tzurbaev , what I recently did is create a trait that I attached to any Test Class and then at the beginning of each method that would call on the remote API I'd specifically overrule the responses. The below code was used for that, please note this requires you to add the Guzzle client to IoC (hence the extend call):

<?php

namespace Testing\Mocks;

use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\History;
use GuzzleHttp\Subscriber\Mock;

trait GuzzleMocked
{
    protected $guzzleHistory;

    protected function mockNextGuzzleRequest(Response $response)
    {
        $this->mockNextGuzzleRequests([$response]);
    }

    protected function mockNextGuzzleRequests(array $responses)
    {
        foreach ($responses as $response) {
            if (!($response instanceof Response)) {
                throw new \RuntimeException("Response type incorrect.");
            }
        }

        $client = new Client;

        $mock = new Mock($responses);

        $client->getEmitter()->attach($mock);

        $this->guzzleHistory = new History();

        $client->getEmitter()->attach($this->guzzleHistory);

        $this->app->extend(Client::class, function () use ($client) {
            return $client;
        });
    }
}

This logic uses a mock subscriber, which attaches to the Guzzle client. We implement a history middleware that tells Guzzle we expect certain responses. The responses can be anything as long as you use the GuzzleHttp\Message\Response object.

The following local API call would trigger a remote API, which I mocked with a simple status 200 response:

    public function testFullStore()
    {
        $this->mockNextGuzzleRequest(
            new Response(200)
        );
        $this->uri = 'account';
        $this->method = "POST";
        $this->parameters = array(
            'name' => 'Donald Duck',
            // .. more
        );
        $this->callResource();
        $this->assertResponseOk();
        // .. more asserts
    }

Hope this helps.

2 likes
tzurbaev's avatar

@luceos sorry for delay.

Your way looks pretty good, I'll give it a try on a weekends, thank you for sharing!

P.S. Only after your answer I realized (and checked) that Laravel actually does not make any HTTP requests to its routes during tests. Silly me.

Please or to participate in this conversation.