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

By | July 4, 2012

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. Chris Hartjes

    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.

    Reply
  2. Peter Galiba

    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.

    Reply
  3. Pingback: Linkpool Nummer 31 | PHP Gangsta - Der PHP Blog mit Praxisbezug

  4. Pingback: strayObject

Leave a Reply

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