I used to be a big fan of filesystem abstractions, not only for the abstraction benefit, but also for the testing benefit as well. It is trivial to unit test classes depending in filesystem abstractions like Flysystem or Gaufrette: just a simple stub or mock of the interface and we are done.
However, from time to time I was kinda annoyed with some limitations of the abstractions, especially with regard to stream handling. I started to get disappointed with them and started looking at simpler approaches. For instance, PHP supports filesystem abstractions natively in the form of stream wrappers. If you don’t know about them, you should really take a look at them!
But there was just one thing that prevented me from going all-in with PHP stream wrappers, and this is that they are really complex to test, because they imply to hit the real filesystem since you cannot mock php filesystem functions.
Well, it turns out not really. Actually, what if I told you that you can use PHP stream wrappers to create an in-memory filesystem for testing purposes? Actually, you don’t even have to create it, because it already exists!
adlawson/vfs implements such a filesystem, which is commonly called a virtual filesystem. The way you use it is very similar to using a mock. You create the filesystem and leave it in the state you want for your tests. For example, here’s a test from one of the Espresso packages where I use it:
<?php
$templateEngineMock = $this->createMock(TemplateEngineInterface::class);$transformerMock = $this->createMock(TransformerInterface::class);$mimeTypesMock = $this->createMock(MimeTypes::class);
// Setting up virtual FS$fs = FileSystem::factory('vfs://');$fs->mount();// We create a new directory with some files in it$dir = new Directory(['bar' => new File('Some file with no extension')]);// Then we add the directory to the fs$fs->get('/')->add('foo', $dir);
$mimeTypesMock->expects($this->once()) ->method('getMimeType') ->with('') ->willReturn(null);
$simpleResponse = new SimpleResponse($templateEngineMock, $transformerMock, $mimeTypesMock);$response = $simpleResponse->withDownload('vfs://foo/bar'); // Here we use itOnce we have set up the filesystem, we can use the vfs:// stream wrapper like any other stream wrapper. This way, we
can test behavior without hitting a real filesystem, which makes our test a truly unit one.
Make sure you start testing the code depending on PHP native filesystem functions this way. You’ll see it’s a lot easier to work with, and way more reliable.