In testing the Aura.Http package, I have realized that it’s much more flexible, testing wise, to pass around file resources (a.k.a. handles, pointers, or streams) than it is to pass around file names. When you do that, you can use a php://memory stream instead of attempting to touch the file system. For example:
<?php
// $this->save('/path/to/file.txt', 'Hello World!');
public function save($file, $data)
{
file_put_contents($file, $data);
}
The test would have to be something like this
<?php
public function testSave()
{
$file = '/path/to/file.txt';
$data = 'Hello World!';
$this->system_under_test->save($file, $data);
$this->assertTrue(file_exists($file));
$actual = file_get_contents($file);
$this->assertSame($data, $actual);
unlink($file);
}
All sorts of things can go wrong with that, starting with file permissions. It’s also against testing dogma, in that you touch the file system.
However, if you rewrite the method to use a file resource instead of a file name …
<?php
// $fp = fopen('/path/to/file.txt', 'w+');
// $this->save($fp, 'Hello World!');
public function save($resource, $data)
{
fwrite($resource, $data);
}
This places control of the file creation in your hands directly, not under the control of the system under test. Then the test looks like this (with a helper method):
<?php
protected function readResource($resource)
{
rewind($resource);
$data = null;
while (! feof($resource)) {
$data .= fread($resource, 8192);
}
return $data;
}
public function testSave()
{
$fp = fopen('php://memory');
$data = 'Hello World!';
$this->system_under_test->save($fp, $data);
$actual = $this->readResource($fp)
$this->assertSame($data, $actual);
}
Voila! No more touching the file system.
You could also use vfsStream https://github.com/mikey179/vfsStream if you don’t want to refactor your code to use resources instead of actual file names. Either way, the goal is to make sure any code that you are testing that does file system operations is not actually writing to the file system.
Thanks for the post and the vfsStream tip, Chris.
Thank you for the post @Paul .
Thank you @Chris for vfsStream
Just please don’t forget to document the side effect of readResource, as it changes the file pointer. Or maybe store the current pointer, then restore it at the end of the function using ftell and fseek. Otherwise it could introduce a bug in your application.
Polymorphism on the cheap! Great to know for existing code that needs to be unit tested.
[...] mit File-Handles/Streams anstatt mit Dateinamen:http://paul-m-jones.com/archives/2487Ein PHP Framework, geschrieben in C als PECL Extension:http://pecl.php.net/package/yafEigentlich [...]
If you need a wider range of “file” streams (Amazon S3, FTP, In-Memory, and many more), take a look at the Gaufrette file stream abstraction library: https://github.com/KnpLabs/gaufrette
Thanks for the post!
Great to know about this!
[...] came across Paul’s article, where he writes about a nifty way to perform tests on methods which deal with filesystem. It is an [...]