vfsStream replaces the filesystem mock you wrote badly

Testing code that reads and writes files usually ends in a tests/tmp directory, a setUp that creates it, a tearDown that recursively deletes it, and a suite that leaves the directory behind whenever something fatals halfway through. vfsStream registers a stream wrapper, so vfs:// paths behave like a filesystem that exists in memory for the length of one test.

use orgbovigovfsvfsStream;

class ConfigLoaderTest extends PHPUnit_Framework_TestCase
{
    private $root;

    public function setUp()
    {
        $this->root = vfsStream::setup('etc', null, array(
            'app.ini'      => "[db]nhost = 127.0.0.1n",
            'app.ini.dist' => "[db]nhost = localhostn",
        ));
    }

    public function testPrefersTheLocalFileOverTheDistributedOne()
    {
        $loader = new ConfigLoader(vfsStream::url('etc'));

        $this->assertSame('127.0.0.1', $loader->get('db.host'));
    }

    public function testReportsAnUnreadableFile()
    {
        $this->root->getChild('app.ini')->chmod(0000);

        $this->setExpectedException('ConfigUnreadable');
        new ConfigLoader(vfsStream::url('etc'));
    }
}

The gain is not speed, though it is faster. It is that nothing survives the test: no directory to clean up, no permissions to arrange on a build server, and no chance of one test seeing a file another test forgot to remove. The second method is the part that is hard to do any other way — chmod(0000) inside a virtual filesystem reproduces an unreadable file without needing a real one and without running as a second user, and vfsStream::setQuota() does the same for a disk that is full. What it cannot do is anything that bypasses the stream wrapper: realpath(), glob() and most of the exec family either ignore vfs:// or behave differently on it, so code that shells out to mv still needs a real directory.