4
votes

After writing PHPUnit tests for a while I've come across a pattern that I'd like to find an elegant solution for. Unit tests for my project use a lot of temp files. I'll have a function that outputs a file, and I need to make several assertions on the output (temp) file. Afterwards I want to make sure that the temp files get cleaned up, even if some of the assertions fail.

Here's an example test file using with some cruft to handle the temp files:

class FooTest extends AhshayTestUnit
{
    function setUp()
    {
        $this->tmps[] = array();
    }

    function tearDown()
    {
        foreach ($this->tmps as $tmp)
        {
            @unlink($tmp);
        }
    }

    function tmp()
    {
        $this->tmps[] = $tmp = tempnam('/tmp', 'test_foo');
        unlink($tmp);
        return $tmp;
    }

    function testFoo()
    {
        $in = 'html2pdf.html';
        $out = $this->tmp();

        $success = html2Pdf($in, $out);

        $this->assertFileExists($out, "should create PDF file");
        $this->assertGreaterThan(1024, filesize($out), "output file should be atleast 1k");

        # and so on
    }
}

This way, each test function doesn't need to worry about deleting $out, even if the first assertion fails.

Now that I've said all that, I'd like to bake these helper functions into every test object I have. I already have my own class that extends PHPUnit_Framework_TestCase. I could put my custom setUp() and tearDown() in my subclass, but it seems a bit lame to require all my tests that have their own setup/teardown to remember to call parent::__construct(). Is there a more elegant way to do this? Is there something already built into PHPUnit to handle this? It seems like the best way would be to have some way for PHPUnit to chain setup/teardown callbacks.

2

2 Answers

3
votes

You can use vfsstream to mock the file system. That's a nice solution that avoids any garbage by design.

A custom solution could be to register a shutdown function which is deleting the temp file(s) even if a fatal error occurred, like in this (simple) example:

class MyTest extends PHPUnit_Framework_TestCase
{

    public function setUp() {
        register_shutdown_function(function() {
            if(file_exists('temp.file')) {
                unlink('temp.file');
            }
        });
    }

    ... the tests

}
0
votes

Registering a shutdown function for cleanup is quite useful when your tests don't clean up after themselves (as they might not during integration testing).

Having said that, if you do find that you're repeating the same logic many times, you can use PHP traits to make something like a TempTrait that just has the functions you need either to run during your tests or during each test's tearDown function.