When Possible, Use File *Resources* Instead Of File *Names*

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.

9 thoughts on “When Possible, Use File *Resources* Instead Of File *Names*

  1. 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.

  2. […] came across Paul’s article, where he writes about a nifty way to perform tests on methods which deal with filesystem. It is an […]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>