Nothing found, try being more general or more specific

Testing your file system with vfsStream

If you take care after your project and code, then you write unit tests. But with them, there are «special cases». Filesystem and resources are one of them. Solving it straight on is done by making separate folder tree just for tests, hoping that they are stable enough to run after fails and that they will not touch and delete actual paths.

A more correct approach is to use in-memory virtual file system, vfs for short. And since resources are, by their nature, streams, so is the name of mock library for it - vfsStream. So lets install it..

composer install mikey179/vfsStream

Now we need to initialize it vfs. Note that all functions operate within the context of its root folder. If you start making virtual folders without providing it, they will not appear in the result tree.

public function setUp() {
    //vfsStream::setup();
    //vfsStreamWrapper::register();
    //vfsStreamWrapper::setRoot(vfsStream::newDirectory('root', 0777));
    //$this->rootDir = vfsStreamWrapper::getRoot();
    $this->rootDir = vfsStream::setup('root', 0777);
}

Tree

Now we need to setup initial tree state..

vfsStream::create([
    'library' => [
        'bb2075d7d7023ebd5929f6a3f4c4d499' => [
            'original.jpg'=>'erferf',
            'size' => [
                '160.jpg',
                '320.jpg'
                ]
            ]
        ]
    ], 
    $this->rootDir
);
unlink(vfsStream::url('root/library/bb2075d7d7023ebd5929f6a3f4c4d499/size/160.jpg'));

#PHPUnit_Framework_Error_Warning : unlink(vfs://root/library/bb2075d7d7023ebd5929f6a3f4c4d499/size/160.jpg): No such file or directory

If we would try to delete the file right away, we would see the error above. That happens because vfs emulates also file permissions and users. So you can't just delete file - the code doesn't know if its the same user that created files in the first place. And thats where second problem appears - there is no short syntax for providing permissions.

So simple and hacky solution would be to create folders and then add files one by one with permissions 777.

vfsStream::newFile('original.jpg', 0777)->setContent('test')->at(
    $this->rootDir->getChild('library')->getChild('bb2075d7d7023ebd5929f6a3f4c4d499')
    //$this->rootDir->getChildByPath('library/bb2075d7d7023ebd5929f6a3f4c4d499')
);

Similarly, there is a method newDirectory for making folders. With rights, there is another problem - how do you see entire tree? Thats what *Visitor classes are for. For example they allow you to format tree as an array..

$result = vfsStream::inspect(new \org\bovigo\vfs\visitor\vfsStreamStructureVisitor())->getStructure();

$this->assertEquals([
    'root' => [
        'library' => [
            'f420b5caa94fb3ac74fe4fb602e38fe8' => []
        ]
    ]
], $result);

I've tried making my own version that would print out a tree as a string, maybe it will be merged into master..

\=root @777
.\=library @777
..\=f420b5caa94fb3ac74fe4fb602e38fe8 @755

Tests

Main problem with getting used to vsf is paths. vfsStream doesn't support chdir() or realpath()
But worse of all, you need to wrap all absolute paths, inside your code.. Meaning you need to refactor what you wrote the first and easy way

vfsStream::url('root/test.txt'); //will become vfs://root/test.txt

That means that you can't work with relative paths

unlink("./readme.txt");

You need to wrap paths with some function. And that means you can't concat path parts. If you do, once you inject path parts from your test, you'll get

PHPUnit_Framework_Error : Object of class orgbovigovfsStreamDirectory could not be converted to string

How do I overcome this? Well' I add wrapper function first. It's not pretty, but it adds some path abstraction. For regular code I return path from the input and for tests I can inject wrapper function that I want

public function removeSizeDir($path){
    if (is_dir($this->fullPath($path . '/size/'))) {
        rmdir($this->fullPath($path . '/size/'));
    }
}

public $pathRewrite = false;

/**
 * Wrap all filesystem access to change path in one place
 * Used actively with unit tests to wrap absolute paths to virtual file system
 *
 * @param string $path
 * @return string
 */
public function fullPath($path){
    if(is_callable($this->pathRewrite)){
        $tmp = $this->pathRewrite;
        return $tmp($path);
    }
    else{
        return $path;
    }
}

This wrapper is injected on test setUp..

public function setUp() {
//..
    $this->o              = new myObjectUnderTest($this->rootDir);
    $this->o->pathRewrite = function ($path) {
        return vfsStream::url('root/' . $path);
    };
}

/**
 * @test
 */
public function removeSizeDir(){
    vfsStream::create([
            'library' => [
                'bb2075d7d7023ebd5929f6a3f4c4d499' => [
                    'size' => []
                ]
            ]
        ], $this->rootDir
    );
    $this->o->removeSizeDir('library/bb2075d7d7023ebd5929f6a3f4c4d499');
    $result   = vfsStream::inspect(new \org\bovigo\vfs\visitor\vfsStreamAssertVisitor())->getStructure();

        $expected = <<<EOF
\=root @777
.\=library @777
..\=bb2075d7d7023ebd5929f6a3f4c4d499 @755
EOF;
        $this->assertEquals($expected, $result, $result);
}

Good & quality code coverage to you :)