Joe's
Digital Garden

PHP Unit

Filtering Tests

The filter flag can be used to specify specific tests in a file or even particular runs in a data provider.

phpunit --filter methodName path/to/file.php

This will run the test methodName in the test file.php

Martin Hujer has some excellent advice for filtering tests using data providers

phpunit --filter "methodName#2" path/to/file.php
phpunit --filter "methodName#0-2" path/to/file.php
phpunit --filter "method@providerName" path/to/file.php

These examples all execute the test methodName in file.php, but in each case they also filter by the data provider case. The first will data provider with index 2, the second will run tests with indexes 0-2, the last will run the named test "providerName"

Delayed Data Provider Execution

Martin Hujer has some excellent advice for using data providers with PHPUnit.

One issue that plagues PHPUnit is that the data provider methods must be called before the set up method in order to determine the total number of tests. If we are using set up to initialize an object under test, we do not have access to that object in the data provider. We can fix this issue by using closures to lazy-execute the returns from the providers:

class DateTimeTest extends TestCase
{
    private $DateTime;

    public function setUp(): void
    {
        $this->DateTime = DateTime::createFromFormat(
            'Y-m-d H:i:s', 
            '2020-08-12 00:00:00',
            new \DateTimezone('UTC')
        );
    }

    /**
    * @dataProvider timeZoneProvider
    */
    public function testAdjustTimeZone(
        string $expected, 
        \Closure $dateTime
    ): void {
        $this->assertEquals(
            $expected, 
            $dateTime()->format('Y-m-d H:i:s')
        );
    }

    public function timeZoneProvider(): array
    {
        return [
            'utc' => [
                'expected' => '2020-08-12 00:00:00',
                'dateTime => function() {
                    return $this->DateTime;
                }
            ],
            'chicago' => [
                'expected' => '2020-08-11 18:00:00',
                'dateTime' => function() {
                    return $this->DateTime->setTimezone('America/Chicago')
                }
            ]
        ];
    }
}

This is a contrived example, but it isn't uncommon to need to set up a complex object in the set up method only to want to mutate it in the provider and then test if the new state of the object matches an expected state.

In this case, if we passed $this::DateTime back directly in the array we would get null since $this::DateTime has not been set yet on the test object. But if we pass a closure which fetches $this::DateTime at the time of the test, we will actually get the object we set up in setUp().

Linked References