Debug Unit Tests using the PhpStorm built-in test runner

PhpStorm comes with a handy phpunit test runner, which lets you execute your tests with just one click. Even better: if a test fails, you can just run that test also with just one click. That’s how I like it.

However I often use the debugger to debug failed tests. This is easy with phpunit and via the command line (see e.g. Remote CLI debugging). But since PhpStorm is obviously not using the command line, we have to pass the information necessary for Xdebug and PhpStorm to initiate the debug session using the Test Runners configuration.

XDEBUG_CONFIG=idekey=PHPSTORM ; PHP_IDE_CONFIG=serverName=local.dev

Needless to mention that the above values need to fit your setup. Most likely your server name will be different, and make sure to check the ide key.

But where to put it? Assuming you already setup your Run/Debug configuration, just open the configuration dialog and put the above environment variables into the “Environment variables” input field. You don’t need to add “export” or anything. Well, just have a look:

phpstorm_unit_test_runner

Now just place your breakpoints as you need them and start the Run/Debug configuration.

Filesystem abstraction in PHP, and why it sometimes is useful!

Phase 1: Ignorance and Bias

Recently I came across Flysystem, which is a Filesystem abstraction layer for PHP. Now if you haven’t worked with a Filesystem abstraction layer before (like me), you may ask yourself:

Why on earth should I add even more overhead for something basic as the file system?

Let me explain why it will be extremely useful. Say we have to build a small app in PHP which should download files from an FTP server, process them in some way, and store the result on the servers filesystem. No doubt, this can be done with PHPs build-in functions without a problem, you might say. Sure, but don’t forget the Unit Tes… dang! See?

Phase 2: Skepticism and Curiosity

The solution for our problem once more is: Abstraction. Abstract the process from any file system. Just use the same set of operations on some sort of adapter, no matter where or how the files are stored, and let the Abstraction Layer do the rest. You guessed it already, this is where Flysystem comes into play! Let’s start with a simple file download:

<?php

namespace My\Cool\Project;

use League\Flysystem\FilesystemInterface;

class Downloader
{
    /**
     * @var FilesystemInterface
     */
    protected $remoteFileSystem;

    /**
     * @var FilesystemInterface
     */
    protected $localFileSystem;

    /**
     * @param FilesystemInterface $remoteFileSystem
     * @param FilesystemInterface $localFileSystem
     */
    public function __construct(
        FilesystemInterface $remoteFileSystem,
        FilesystemInterface $localFileSystem
    ) {
        $this->remoteFileSystem = $remoteFileSystem;
        $this->localFileSystem = $localFileSystem;
    }

    /**
     * Download files from the remote file system
     * @param string $fileName The filename
     * @return bool
     */
    public function download($fileName)
    {
        $fileDownloaded = false;
        
        // This is the actual download part!
        if (!$this->localFileSystem->has($fileName)) {
            $fileDownloaded = $this->localFileSystem->writeStream(
                $fileName,
                $this->remoteFileSystem->readStream($fileName)
            );
        }
        // Done!

        return $fileDownloaded;
    }
}

Is that it?? Aww yeah, that’s it. When it’s with Flysystem, we only need a few lines of code, because it’s so intense!

To make the code it more clear, I called the filesystems $localFileSystem and $remoteFileSystem, but of course both filesystems could be remote ones, e.g. FTP and Amazon S3. This means that by using Flysystem, you completely decouple your code from the filesystem, so you’re prepared when the Business suddenly decides to “implement some totally easy changes”.

Of course our example is not 100%ly done, since we still have to create the Filesystem adapters we pass upon instantiation. But that’s ok, we need to do the configuration somewhere anyway. Below function is part of another class, which makes use of our Downloader class:

public function downloadSomeFile()
{
    $ftpAdapter = new \League\Flysystem\Adapter\Ftp([
        'host' => 'ftp.server.com',
        'username' => '123',
        'password' => 'abc'
    ]);
    $remoteFileSystem = new \League\Flysystem\Filesystem($ftpAdapter);

    $localAdapter = new \League\Flysystem\Adapter\Local('/tmp');
    $localFileSystem = new \League\Flysystem\Filesystem($localAdapter);

    $downloader = new \My\Cool\Project\Downloader(
        $remoteFileSystem, 
        $localFileSystem
    );
    
    $downloader->download('somefile.txt');
}

This is what I also like about using Flysystem: within the Downloader class we don’t have to deal with any path settings or such. It is completely done within the code which instantiates the Downloader, i.e. in the domain where it belongs.

it's so decoupled

Phase 3: Awesomeness and Why-didn’t-I-use-it-before-ness

I assume most of you didn’t even read until here, since you’ve been totally blown away and immediately started refactoring your code, giggling slightly insane. But wait, we’re not even fully through yet. Remember the question about Unit Tests? With Flysystem, it’s a breeze:

public function testCanDownloadFileFromRemoteFileSystem()
{
    // This mocks the remote server (could e.g. be an FTP) where we want to 
    // download the file from
    $remoteAdapter = new \League\Flysystem\Vfs\VfsAdapter(new Vfs);
    $remoteFileSystem = new \League\Flysystem\Filesystem($remoteAdapter);

    // Put a file on the virtual remote filesystem
    $remoteFileSystem->put('somefile.txt', 'just some content, does not matter');

    // Virtualize our local file system also, so no need to clean up or anything, 
    // since by using vfsStream, we don't even have to take care where to store 
    // the files during our tests.
    $localAdapter = new \League\Flysystem\Vfs\VfsAdapter(new Vfs);
    $localFileSystem = new \League\Flysystem\Filesystem($localAdapter);

    $downloader = new \My\Cool\Project\Downloader(
        $remoteFileSystem, 
        $localFileSystem
    );

    $this->assertTrue($downloader->download('somefile.txt'));
    $this->assertTrue($localFileSystem->has('somefile.txt'));
}

Flysystem even provides an adapter for vfsStream, a stream wrapper for PHP which helps greatly when you have to test filesystem-related functionality.

In simple terms, it simulates a file system in memory, which many (but not all) PHP functions can use.

Check it out here if you don’t know it yet: vfsStream on Github

So here we are: we just wrote a test for the FTP download in 5 minutes – without having to change a single line in the Downloader, without relying on a real FTP server. We could exchange the remote filesystem with Dropbox or even your Grandma’s closet within two minutes. Awesome. Now get a coffee and head over to your co-worker, who’s still struggling with the Downloader class and PHPs lovely native FTP support.

Mocking magic methods with PHPUnit

If you want to test a method of an object which is supplied using the magic method __call(), you can’t simply mock the desired method. You have to go the longer way via __call(). So e.g. if your magic method you want to mock is called “myMagicFunction”, you could be tempted to write

$mock
    ->expects($this->once())
    ->method('myMagicMethod')
    ->with('123')
    ->willReturn('myResult');

since, after all, it is a mock, right? So why wasting time? Obviously PHPUnit (Version 4.5.0 at the time of writing) is doing some sort of internal checks, preventing our laziness and forcing us to mock the __call() method instead. I can only guess the reason here, any comments with more insight on this topic are highly appreciated!

Of course, mocking the __call() method instead is not much more work

$mock
    ->expects($this->once())
    ->method('__call')
   // Be sure to pass the method argument(s)
   // as array, even if you only have one 
   // argument!
   ->with(
      $this->equalTo('myMagicFunction'),
      $this->equalTo(['123'])
   )
   ->willReturn('myResult');

However PHPUnit will not throw any Exceptions if you use the first approach, instead it will only return NULL instead the expected ‘myResult’. This can be a bit frustrating 🙂