Mocking the file system using PHPUnit and vfsStream

This article is about how to mock the file system when writing unit tests, and it will be rather code-heavy. If you are not familiar with the concept of unit testing this article might not be the best place to start. There will be other articles regarding unit testing on this blog, so keep coming back for more.

Those of you still reading are hopefully familiar with unit testing. PHPUnit is the de-facto standard for unit testing in PHP projects, and this is what we will be using together with vfsStream in this article.

vfsStream is a custom stream wrapper for PHP that works as a virtual file system that PHP’s built in filesystem functions can use. Some functions that work with vfsStream are:

There are some known limitations when using custom stream wrappers. Some functions that does not work with vfsStream:

vfsStreams’ known limitations are listed on its wiki. Now, on to the code!

The following simplified class is the class we will be testing using PHPUnit and the vfsStream stream wrapper:

PS! The class is very simplified and does not include error handling at all. It should only be used for instructional purposes.

As you can see the class uses regular file system functions such as copy, unlink, and file_get_contents. To be able to get full code coverage on this class all these functions need to work their magic.

A typical way to solve this when writing tests is to create files/folders in PHPUnit’s setUp() method and remove them again in the tearDown() method. This works, but if your tests somehow dies (fatal error for instance) during a test run you are left with files/folders on disk that was never cleaned up. vfsStream does not touch the file system at all, but keeps virtual files and folders in memory, so if your test suite dies there is nothing on disk that was not there before you started the tests. Also, if you manage to sneak in a bug that deletes more files than it should it’s safer to work on a virtual file system than an actual one.

Now, lets take a look at the basics of vfsStream. First you will need to install it. This can be done easily using PEAR:

After installation make sure the vfsStream directory is in your include path. If you have a sane PEAR environment you are most likely good to go.

The following snippet will show you the basics of vfsStream:

First we simply require the main vfsStream class (that in turn will require other components as well). The next three lines registers the stream wrapper (which defaults to vfs://) and creates the root directory which vfsStream will use as a container for other virtual files and directories. These three lines can be replaced by:

which is a convenience method that will be used in the unit tests later on.

The vfsStream::url method that is used in the code snippet above creates the path that the file system functions must use. It will simply prepend the schema to the file/directory name.

The next few lines in the code snippet just illustrates how the regular file system functions work with vfsStream.

To add a file we first create a new vfsStreamFile object by calling vfsStream::newFile() which simply takes a filename as argument. We attach it to the root directory by using the root object’s addFile method. After this we do some file system checks for the file, remove it, and check again. As you can see all this is pretty similar to working with regular files.

Now that we have the basic usage of vfsStream out of the way, let’s move on to how to incorporate this into our tests. The below code snippet is the test class without any actual tests as we’ll add them later on.

As you can see from the code snippet we use the vfsStream::setup method to create a root dir, and set that dir as the rootDir in the driver. No action is needed in the tearDown() method regarding vfsStream. The first test we will write shall test the store method.

First we create a random hash value that will be used to identify the file internally in our driver. Then we make sure the file does not exist prior to running the store method. After storing the file we assert that it now exists. The vfsStreamWrapper::getRoot() method returns the root vfsStreamDirectory object, on which we call the hasChild() method to see if it has a child with a given name.

Next up is the delete method:

This is pretty much the same as the previous test method except that we register a virtual file before calling delete(). After deleting we assert that the file is actually gone. Now for the last test:

In this last test method we create a virtual file with content by using the setContent() method on the vfsStreamFile object. After adding the virtual file we assert that we get the same content when fetching the file identified by $hash.

And that’s that! If anyone has used vfsStream differently or has related questions, don’t hesitate to leave a comment. Happy testing!

Related links:

Read more from the Software engineering category