download

Mock third party API’s with PHPUnit

By Chris Taylor, published Tuesday, February 3rd, 2015


Well sort of. This is an example of how to use a wrapper class for a third party API to make it easier to write and test your application. In your unit tests you should not actually call any third party API’s, or any kind of API for that matter. You should mock it.

In this example I’ll show you a simple class that is used to access the AWS S3 services API, and then a simple class for testing it.

Using a class like this allows you to limit the functionality of any requests to the API as well as allowing you to abstract your code from the API.

<?php

namespace Util;

use Aws\S3\S3Client;
use Guzzle\Common\Exception\InvalidArgumentException;

/**
 * The S3Utils is used to allow access to the AWS S3 API
 *
 * @package Util
 */
class S3Utils
{

    /**
     * A S3Client for use in this class
     * @var S3Client $client
     */
    protected $client;

    /**
     * An array of config options for this class
     * @var array $config
     */
    protected $config;

    /**
     * Constructs a S3Utils
     * @param S3Client $s3Client
     * @param array $config
     */
    public function __construct(S3Client $s3Client, array $config)
    {

        $this->config = $config;

        $this->client = $s3Client;
    }

    /**
     * Gets a signed url for an export object on s3
     * @param string $exportObjectName
     * @return string
     * @throws \InvalidArgumentException
     */
    public function getSignedUrlForExportObject($exportObjectName)
    {

        if ($this->client->doesObjectExist($this->config['bucket-name'], $exportObjectName)) {

            try {

                return $this->client->getObjectUrl($this->config['bucket-name'], $exportObjectName, $this->config['allowed-time']);

            } catch (InvalidArgumentException $e) {
                throw new \InvalidArgumentException('Export link could not be generated');
            }
        } else {
            throw new \InvalidArgumentException('Export file does not exist');
        }
    }

}

In this class there is only one function and it’s used to allow the generation of a signed URL to an object in a specific bucket on S3. This abstraction allows you to not worry about a few major variables when asking for a link, like which bucket it’s in, and how long the link is valid for, as it’s set in the config options.

Now let’s write a few tests to make sure our class is correct:

<?php

class S3UtilsTest extends PHPUnit_Framework_TestCase
{

    public function setUp()
    {

        $this->app['aws.s3.class'] = $this->getMockBuilder('\Aws\S3\S3Client')
            ->disableOriginalConstructor()->getMock();

        $this->app['aws.s3.config'] = [
            "bucket-name" => "get-files-here",
            "allowed-time" => "+10 minutes"
        ];
    }

    /**
     * @expectedException InvalidArgumentException
     * @expectedExceptionMessage Export file does not exist
     */
    public function testGetSignedUrlForExportObjectDoesntExist()
    {

        $this->app['aws.s3.class']
            ->expects($this->once())
            ->method('doesObjectExist')->will($this->returnValue(false));

        $this->app['aws.s3.class']
                    ->expects($this->never())
                    ->method('getObjectUrl');

        $utils = new Util\S3Utils($this->app['aws.s3.class'], $this->app['aws.s3.config']);
        $utils->getSignedUrlForExportObject("Something that doesn't exist");
    }

    public function getSignedUrlForExportObjectSuccess()
    {

        $this->app['aws.s3.class']
            ->expects($this->once())
            ->method('doesObjectExist')
            ->will($this->returnValue(true));

        $this->app['aws.s3.class']
            ->expects($this->once())
            ->method('getObjectUrl')
            ->will($this->returnValue("url"));

        $utils = new Util\S3Utils($this->app['aws.s3.class'], $this->app['aws.s3.config']);

        $this->assertEquals("url", $utils->getSignedUrlForExportObject(null));
    }

}

Although this is a short example, the principles behind it can be applied to any situation. A level of abstraction can be used to remove your code from API’s or libraries that may be difficult to mock or test. In this instance you can use your class S3Utils in other classes without needing to worry about mocking the S3 class and risk actually calling the API in your tests.