Testing File Downloads with Guzzle

I have a class that does two things: downloads a call recording as an MP3 from Twilio and then re-uploads it to Amazon S3 for permanent storage. This scenario happens frequently, and I wanted to be able to test it without actually making a real HTTP request from Twilio or to Amazon S3. In this article, I’ll explain the open source libraries I used, as well as how to configure them.


To begin, I installed the following libraries (taken directly from my composer.json file):

  • "adlawson/vfs": "^0.12.1"
  • "guzzlehttp/guzzle": "^6.3"

The adlawson/vfs package is installed as part of the require-dev block rather than the require block because it is only necessary as part of your tests. VFS stands for virtual filesystem, and this library allows you to mount a virtual filesystem that your test code can write data to. The virtual filesystem can then be torn down at the end of the test without any impact to your actual filesystem. PHP has a powerful feature built-in to the language called “streams“. The VFS library makes use of streams to read and write files directly in memory which mimics a real filesystem.

Guzzle is a very nice HTTP client. It provides a nice wrapper for interacting with HTTP endpoints, but more importantly, provides a way to mock HTTP requests and responses.


Let’s start by looking at the code we need to test.

namespace MyApp\Library\Calls;

use MyApp\Entity\Call;
use MyApp\Library\Messengers\TwilioClient;
use MyApp\Library\Storage\Uploader;

use GuzzleHttp\Client as GuzzleClient;

class CallAudioProcessor

    /** @var GuzzleHttp\Client */
    private $guzzleClient;

    /** @var MyApp\Library\Messengers\TwilioClient */
    private $twilioClient;

    /** @var MyApp\Library\Storage\Uploader */
    private $uploader;

    /** @var string */
    private $fileDirectory;

    public function __construct(
        TwilioClient $twilioClient,
        Uploader $uploader,
        GuzzleClient $guzzleClient,
        string $fileDirectory
        $this->twilioClient = $twilioClient;
        $this->uploader = $uploader;
        $this->guzzleClient = $guzzleClient;

        $this->fileDirectory = $fileDirectory;

    public function processCallRecording(Call $call) : Call
        // Attempt to get the recording from Twilio.
        $recording = $this->twilioClient->fetchCallRecording(

        // Generate the path where the recording file will be downloaded.
        $filePath = $this->fileDirectory . $recording['fileName'];

        $this->guzzleClient->request('GET', $recording['recordingUrl'], [
            'sink' => $filePath

        if (is_file($filePath)) {
            $fileUrl = $this->uploader->uploadFile(



        return $call;


The test will also be responsible for mocking the TwilioClient and Uploader classes so they can return mocked data.

We’re interested in a test that tests the processCallRecording() method. We want to test the successful path, that is: a recording was found and successfully uploaded to a remote storage location.


Now that our system under test, or SUT, is properly defined, we can take a look at the unit test that runs it.

namespace MyApp\Tests\Calls;

use MyApp\Entity\Call;
use MyApp\Library\Messengers\TwilioClient;
use MyApp\Library\Storage\Uploader;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler as GuzzleMockHandler;
use GuzzleHttp\HandlerStack as GuzzleHandlerStack;
use GuzzleHttp\Psr7\Response as GuzzleResponse;

use Vfs\FileSystem as VirtualFileSystem;
use Vfs\Node\File as VirtualFile;

use Faker\Factory as Faker;

use PHPUnit\Framework\TestCase;

class CallAudioProcessorTest extends TestCase

    public function testProcessingCallRecording()
        $faker = Faker::create();

        // Create the mocks for the TwilioClient and Uploader.
        $twilioClient = $this->createMock(TwilioClient::class);
            'fileName' => sprintf('%s.mp3', md5(uniqid())),
            'recordingUrl' => $faker->url

        $uploader = $this->createMock(Uploader::class);

        // Mount the VFS. In a non-test environment, the $fileDirectory
        // might be something like /var/data/files, but in the test,
        // it is the URL to a stream created by the VFS.
        $fileDirectory = 'vfs://';

        $vfs = VirtualFileSystem::factory($fileDirectory);

        // Create the mock for the GuzzleClient.
        $guzzleMockHandler = new GuzzleMockHandler([
            new GuzzleResponse(200)

        $guzzleClient = new GuzzleClient([
            'handler' => GuzzleHandlerStack::create(

        $callAudioProcessor = new CallAudioProcessor(
            $twilioClient, $guzzleClient, $uploader, $fileDirectory

        $call = new Call;

        $call = $callAudioProcessor->processCallRecording($call);


        // Important to unmount the VFS
        // so other tests can use it.


The test starts by mocking the TwilioClient and Uploader classes. These are straightforward mocks that have methods expected to be called once and that will return some basic information.

After the mocks are created, the VFS is mounted. You can make up any mount point you want, and I use vfs:// which I think makes sense. Once mounted, the mock GuzzleClient is created. The native PHPUnit mocking library is not used here, and instead the mock handler provided by Guzzle is used. Though it is not terribly important in this test, the mock handler from Guzzle provides greater flexibility in how your tests behave which is why I prefer it over a PHPUnit mock.

At this point, our fixtures are defined properly and we’re ready to execute the test itself. You can see that a new instance of the CallAudioProcessor class is instantiated with the mocks we’ve built as arguments to the constructor.

Note: If this were a functional test and you were instantiating the CallAudioProcessor from a dependency injector (like the one used in Symfony), you would have to provide setters that allow you to override the depended on components (or DOCs) passed into the constructor.

Finally, the method we’re testing, processCallRecording(), is called, and we assert that the code worked as intended.

Important: You must unmount the VFS at the end of the test! If you fail to do this, any subsequent test using the same VFS scheme (vfs:// in this example) will fail. Behind the scenes, the VFS library destroys the stream when unmount() is called. If this is not done, a subsequent test will attempt to re-create an identical stream, and PHP will complain.


Interacting with the filesystem is always a pain-point when writing tests (regardless of the test type: unit or functional). A virtual filesystem is a beautiful way to alleviate this pain, and it works great for all test types (my functional tests make use of them extensively too).

Unit Testing Your Service Layer is a Waste of Time

Writing unit tests for your application’s service layer is a waste of your time and won’t strengthen your application any better than functional tests would. Writing functional tests that thoroughly test your application does what it says it will do correctly is a much better use of your resources.

I first learned about test driven development (TDD) and automated testing in 2009 and was blown away. I immediately purchased the book xUnit Test Patterns and devoured it. I learned about code smells and fixtures and stubs and mocks and everything in-between. It was amazing that I could actually verify that my application works like I intended it to. Throw TDD in the mix where I could guarantee that I only write as much code as necessary to pass my tests and I was hooked.

I immediately took an academic approach to testing. Invert every components control, mock every depended on component (DoC), completely isolate and unit test everything: the database, other objects, even the file system.

For trivial applications like a project hacked together on a weekend, this practice was fine. I felt like I spent an equal amount of time writing tests as I did writing the code itself.

Fast forward a few years and I have a successful consultancy and am working full time writing web and network applications. I’m still trying to write unit tests and follow TDD principles, but it just isn’t working.

For example, I would write both unit and functional tests for my application’s controllers. For a Symfony application, this is a non-trivial task as the number of depended on components in each controller can easily force your tests to be longer than the controller code itself. Breaking controllers into very small units is difficult as they often deal with handling UI interactions and must communicate to multiple components of the application.

Application Development Has Evolved

When TDD and automated testing became integrated with object oriented programming, databases were big behemoths that required a full time team to manage. File systems were very slow and not reliable. It made sense that your tests should be comprised of small units that were tested in isolation: your components were unreliable!

Today, enterprise applications are contained in a single repository. You can mirror a production server on your insanely powerful laptop with a single Vagrantfile. Working with databases in your tests is a cinch (especially with tools to build out your fixtures in YAML). In all but a few instances, you generally don’t need to worry about a network device or disk drive failing during test suite execution. And modern frameworks employ multiple environments with a dependency container that makes it very simple to mock complex components in your test suite.

Application development has evolved, and the time spent writing unit tests that mock every component in your system does not offer the same dividends as writing a simple functional or integration test does.

Motivating Example

Lets look at a small example of how writing a simple functional test can rapidly increase development and still provide the same guarantees your code works.

If you recall from my previous article on using the ORM, I am in the process of building an importer for an online store. I have the code written, and now I want to verify it is correct (I’ve thrown any notion of TDD out of the window).

In the first example, all depended on components of the system under test (SUT) are mocked.


use MyApp\Library\Feeds\ProductImporter;

class ProductImporterTest extends PHPUnit_Framework_TestCase

    public function testImportingProducts()
        // Imagine this was a sample array of data.
        $results = [];

        // Spend the next N lines mocking all of your DOCs.
        $db = Mockery::mock('Doctrine\DBAL\Connection')

        $importer = new ProductImporter($db);

        $this->assertEquals(4, $importer->getRecordCount());


The first example is academic; it’s pure. It proves my code works, it’s fast, and it tests individual units. The second example is functional. I’m taking actual files, running them through my code, and seeing how the code responds.


use MyApp\Tests\TestCase;

class ProductImporterTest extends TestCase

     * @dataProvider providerProductFile
    public function testImportingProducts($file, $recordCount, $hasError)
        $fileContents = file_get_contents(__DIR__ . '/' . $file);

        $importer = $this->getContainer()

        $this->assertEquals($recordCount, $importer->getRecordCount());
        $this->assertEquals($hasError, $importer->hasError());

    public function providerProductFile()
        $provider = [
            ['products.invalid.json', 0, true],
            ['products.small.json', 1, false],
            ['products.large.json', 1000, false]

        return $provider;


Another benefit of the second example is that the data provider can grow over time. Client deliver a malformed file and you want to see how the code responds? Throw it in the provider. Client thinks there’s an issue with your importer because their file matches your spec? Throw it in the provider. It makes finding actual bugs that a unit test would completely ignore.

When to Write Unit Tests

There are, of course, times when writing unit tests is necessary and writing functional tests may be impossible.

If you are writing a library or framework, it is wise to write unit tests. You don’t know how your code will actually be used, so having formal verification that your library classes do what they say they will is a must. It also adds a great deal of built in documentation to your codebase because a passing test, by definition, accurately documents your code.

Another time to write unit tests would be for a legacy codebase right before a refactoring. If the codebase is old enough, your DoC’s may be very difficult to work with and thus writing a unit test will accurately capture the functionality of your application.

From the moment I started writing tests, I’ve attempted to get every developer at every company I’ve worked for to start writing tests without much luck. In retrospect, I feel if I had started with a “functional first” approach, I would have been more successful in my efforts. Introducing a developer to testing by way of writing simple functional tests may be the best bet to get all developers writing tests, period.