PHP - - By Reza Lavaryan

Hassle-Free Filesystem Operations during Testing? Yes Please!

When working with the filesystem in our tests suites, a big concern is cleaning up the temporary files after each test runs. However, if for any reason the test’s execution is interrupted before the cleanup phase, further tests might fail, as the environment has not been cleaned up.

In this post, we will use a library named vfsStream to create filesystem mocks. It’s little more than a wrapper around a virtual filesystem, which also works very nicely with PHPUnit.

Note This post requires a basic understanding of unit testing and PHPUnit.

To have something to test, we’ll write a simple class for creating files:

<?php

namespace App\Tests;

class FileCreator extends PHPUnit_Framework_TestCase
{

    public static function create($path, $name, $content)
    {
        $file_name = rtrim($path, '/') . '/' . $name;

        if (file_put_contents($filename, $content)) {
            return true;
        }
        return false;
    }
}

Inside the class, we have a create() method, which accepts the filename, content, and a path to which to save it.

To test the class, we first take the traditional approach, and create a test class without using a virtual filesystem. Then, we’ll bring vfsStream into the game to see how it can simplify the testing process for us.

Traditional Approach

<?php

namespace App\Tests;

class FileCreatorTest extends PHPUnit_Framework_TestCase
{
    protected $path;

    public function setUp()
    {
        $this->path = sys_get_temp_dir();
    }

    public function tearDown()
    {
        if (file_exists($this->path . '/test.txt')) {
            unlink($this->path . '/test.txt');
        }
    }

    public function testCreate()
    {
        $this->assertTrue(FileCreator::create($this->path, 'test.txt', 'Lorem ipsum dolor sit amet'));
        $this->assertFileExists($this->path . '/test.txt');
    }
}

In the above, we define the temporary directory (in setUp()) which we’ll use for our temporary files by using PHP’s sys_get_temp_dir(). The function returns the system’s directory path for temporary files.

Then, we test our class’ functionality by making two assertions and finally, we clean the environment up (in tearDown()) for the next set of tests.

There’s nothing wrong with this code in its current state. But what if we have to work with many files in each test run? What if the code breaks before we get the chance to remove the temporary files? vfsStream has the answers to these questions.

Using vfsStream

In this section, we’ll rewrite FileCreatorTest with the help of vfsStream.

To install it, we use Composer:

composer require mikey179/vfsStream

Here’s the test class with vfsStream:

<?php

use org\bovigo\vfs\vfsStream;

class FileCreatorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var org\bovigo\vfs\vfsStreamDirectory
     */
    protected $vfs;

    public function setUp()
    {
        // Setting up vfsStream in our test class
        $this->vfs = vfsStream::setup('testDirectory');
    }

    public function testCreate()
    {
        $this->assertTrue(FileCreator::create(vfsStream::url('testDirectory'), 'test.txt', 'Lorem ipsum dolor sit amet'));

        $this->assertFileExists($this->vfs->getChild('test.txt')->url());
    }
}

In setUp(), we set up vfsStream by calling vfsStream::setup(). This method sets up vfsStream, and returns an instance of vfsStreamDirectory. This is our directory container.

The directory container is actually a filesystem mock with one main directory called testDirectory.

In the first assertion, we pass the main virtual path (by using vfsStream::url()) to FileCreator::create() along with the filename and its content:

<?php

// ...

$this->assertTrue(FileCreator::create(vfsStream::url('testDirectory'), 'test.txt', 'Lorem ipsum dolor sit amet'));

In the next assertion, we check for the existence of test.txt. To do this, we retrieve the file object (test.txt) from the directory container and get its full path:

<?php

// ...

 $this->assertFileExists($this->vfs->getChild('test.txt')->url());

As you can see, there’s no sign of any cleanup in our test class as all the filesystem operations (creation and cleanup) are handled by vfsStream. If for any reason the code breaks, there’s still no need to do any cleanup as everything is happening in memory.

Conclusion

Apart from what we’ve seen here, vfsStream has a lot more to offer, including but not limited to handling permissions, ownerships, handling large files and complex directory structures, etc. To learn more about it, see their official documentation or wait for some upcoming advanced tutorials on SitePoint!

What do you think about this approach to testing filesystem operations? Do you use an alternative solution? Anticipate any limitations you might run into when using this approach? Let us know!

Sponsors