Using var_export to unit test existing code

When writing unit tests, I find that the most time consuming (and the most boring) part of it all is setting up your data providers. Normally I’ve finished writing the code and verified that it works by hand, and am looking to add regression tests.

Fortunately, if you know that your code works, there’s an easy way to generate data for your providers thanks to var_export.

Imagine you have some code that takes multiple rows and merges them into one parent row, with child objects. Something that looks a bit like this:

# grouped.php
function groupRows($rows){
    $groupedRows = array();
    foreach ($rows as $row){
        $groupField = $row['id'];
        if (!isset($groupedRows[$groupField])){
            $groupedRows[$groupField] = array(
                "id" => $row['id'],
                "content" => $row['content'],
                "subcontent" => array()
            );
        }

        $groupedRows[$groupField]['subcontent'][] = $row['subcontent'];

    }

    return array_values($groupedRows);
}

Given the following input:

array(
    array("id" => 1, "content" => "Foo", "subcontent" => "Bar"),
    array("id" => 1, "content" => "Foo", "subcontent" => "Baz"),
    array("id" => 2, "content" => "Bee", "subcontent" => "Boo")
);

We expect the following output:

array(
    array("id" => 1, "content" => "Foo", "subcontent" => array("Bar", "Baz")),
    array("id" => 2, "content" => "Bee", "subcontent" => array("Boo"))
);

Taking the input and creating the expected output by hand can become quite tiresome once you have more than a few possible inputs. Let’s create a data provider that contains our inputs, and specifies null as our output.

# groupTest.php
require 'grouped.php';

class groupTest extends PHPUnit_Framework_TestCase {

    /**
     * @dataProvider groupDataProvider
     */
    public function testGroup($input, $expected)
    {
        $actual = groupRows($input);
        $this->assertEquals($expected, $actual);
    }

    public function groupDataProvider(){
        $data = array();

        $data[] = array(
          array(
            array("id" => 1, "content" => "Foo", "subcontent" => "Bar"),
            array("id" => 1, "content" => "Foo", "subcontent" => "Baz"),
            array("id" => 2, "content" => "Bee", "subcontent" => "Boo")
          ),
          null   
        );

        return $data;
    }
}

You’ll see a failure as our output is not equal to null.

1) groupTest::testGroup with data set #0 (array(array(1, 'Foo', 'Bar')), NULL)
Array (...) does not match expected type "NULL".

This is where var_export comes in. Before the assert, add the following line:

var_export($actual); die;

Run your test again and the output of your function will be shown in the terminal. It’s in a format that you can copy and paste as valid PHP, so copy it and paste it into your data provider instead of null. Comment out the var_export line and run your test again. It should pass this time. var_export adds new lines, array indices and trailing commas, so feel free to clean the output up a little if you want. Don’t forget that it’s still valid if you want to copy and paste it straight in though.

Congrats! You just used var_export to generate test data. Repeat this process a few more times with more example inputs to build up your test case and you’re done.

Here’s me working through it:

Michael is a polyglot software engineer, committed to reducing complexity in systems and making them more predictable. Working with a variety of languages and tools, he shares his technical expertise to audiences all around the world at user groups and conferences. You can follow @mheap on Twitter

Thoughts on this post

Gary 2014-01-08

This is very similar to what I do, but I use serialize and unserialize. Good post tho Mike.

michael 2014-01-08

I hadn’t though about using serialize/unserialize before, that’d work just as well (or better, as it’s more compact). The advantage that var_export has is that it’s valid PHP code from the start, so it’s easy to copy and paste, then optionally tweak as you see fit

Leave a comment?

Leave a Reply