Example

Frank Kleine edited this page Apr 13, 2015 · 7 revisions

Learn from an example

So, what is vfsStream good for? The best way to show is with a little example. Suppose, we have class like this:

<?php
namespace org\bovigo\vfs\example;
class Example
{
    /**
     * id of the example
     *
     * @var  string
     */
    protected $id;
    /**
     * a directory where we do something..
     *
     * @var  string
     */
    protected $directory;

    /**
     * constructor
     *
     * @param  string  $id
     */
    public function __construct($id)
    {
        $this->id = $id;
    }

    /**
     * sets the directory
     *
     * @param  string  $directory
     */
    public function setDirectory($directory)
    {
        $this->directory = $directory . DIRECTORY_SEPARATOR . $this->id;
        if (file_exists($this->directory) === false) {
                mkdir($this->directory, 0700, true);
        }
    }

    // more source code here...
}
?>

Of course we want to have a test case for the setDirectory() method. How do we test that the directory is created when we set the directory? Here is how you could do it the old way:

<?php
namespace org\bovigo\vfs\example;
class ExampleTestCaseOldWay extends \PHPUnit_Framework_TestCase
{
    /**
     * set up test environmemt
     */
    public function setUp()
    {
        if (file_exists(dirname(__FILE__) . '/id') === true) {
            rmdir(dirname(__FILE__) . '/id');
        }
    }

    /**
     * clear up test environment
     */
    public function tearDown()
    {
        if (file_exists(dirname(__FILE__) . '/id') === true) {
            rmdir(dirname(__FILE__) . '/id');
        }
    }

    /**
     * test that the directory is created
     */
    public function testDirectoryIsCreated()
    {
        $example = new Example('id');
        $this->assertFalse(file_exists(dirname(__FILE__) . '/id'));
        $example->setDirectory(dirname(__FILE__));
        $this->assertTrue(file_exists(dirname(__FILE__) . '/id'));
    }

}
?>

We just use the old-fashioned way and check if the directory was really created in the file system. In the setUp() and tearDown() method we make sure that before and after the test the directory does not exist.

This solution has several drawbacks. One of them is that in case our test dies before the tearDown() method is called the directory will stay in the file system. While this may not hurt in the example above there are scenarios where it is important that the clean up will be done.

But how can we prevent such problems? The best way would be, if there would never be any real operation in the file system. I mean, you do not use real databases in unit tests, why should you use the real file system then? This is where vfsStream comes into play. The same test using vfsStream could look like this:

<?php
namespace org\bovigo\vfs\example;
use org\bovigo\vfs\vfsStream,
    org\bovigo\vfs\vfsStreamDirectory;
class ExampleTestCaseWithVfsStream extends \PHPUnit_Framework_TestCase
{
    /**
     * @var  vfsStreamDirectory
     */
    private $root;

    /**
     * set up test environmemt
     */
    public function setUp()
    {
        $this->root = vfsStream::setup('exampleDir');
    }

    /**
     * test that the directory is created
     */
    public function testDirectoryIsCreated()
    {
        $example = new Example('id');
        $this->assertFalse($this->root->hasChild('id'));
        $example->setDirectory(vfsStream::url('exampleDir'));
        $this->assertTrue($this->root->hasChild('id'));
    }
}
?>

This has several advantages: no tearDown() method is required any more, nothing happens on the real file system but just in memory. The test case itself is much shorter, and with vfsStream you can guarantee and control what the file system environment will look like, as it is completely virtual and not influenced by any other operation that might take place while executing the test.

Note: when checking children with hasChild() or retrieving children with getChild() it is possible to specify a complete path, not only a single child:

<?php
// assume $directory is has url vfs://root/path/to/directory
if ($directory->hasChild('foo/bar/baz.txt')) {
    // retrieve file permissions of vfs://root/path/to/directory/foo/bar/baz.txt
    $bazPermissions = $directory->getChild('foo/bar/baz.txt')->getPermissions();
}