1

I know I can use dataProvider like so:

use PHPUnit\Framework\TestCase;

class DataProviderSetupTest extends TestCase {
    /**
     * @dataProvider basicDataProvider
     */
    public function testDataProvider(string $expected): void
    {
        $actualResult = 'I am the value!'; // generated by some service under test
        $this->assertSame($expected, $actualResult);
    }

    public function basicDataProvider(): \Generator
    {
        yield 'no state, all fine' => ['I am the value!'];
    }
}

This test will run fine.

Yet if I have a property on the test class which is initialzied via setUp, it is null when the dataProvider is executed:

use PHPUnit\Framework\TestCase;

class DataProviderSetupTest extends TestCase
{
    protected string $state;

    protected function setUp(): void
    {
        $this->state = 'Thank you for the fish!';
    }

    /**
     * @dataProvider statefulDataProvider
     */

    public function testDataProviderWithStateDependency(string $expected): void
    {
        $actualResult = 'Thank you for the fish!'; // generated by some service under test
        $this->assertSame($expected, $actualResult);
    }

    public function statefulDataProvider(): \Generator
    {
        yield 'member not initialized' => [$this->state];
    }
}

This test will fail:

The data provider specified for DataProviderSetupTest::testDataProviderWithStateDependency is invalid.
Error: Typed property DataProviderSetupTest::$state must not be accessed before initialization

How to initialize test classes members so that I can use them within data providers?


I also tried setUpBeforeClass yet since that one is static, I cannot set member variables.


My phpunit version is 9.6.11.

2
  • Github issue on phpunit's project site Commented Feb 17 at 12:08
  • Yes, data providers are static methods, and yes, there is setUpBeforeClass(). If you need a (data) "providing" function otherwise, make it private and call it in the objects' test-method as the object already exists then. Commented Feb 17 at 13:16

2 Answers 2

1

This is not possible within phpunit, as they need the current behavior so they can compute the total number of tests.

See this closed phpunit issue for reference.


There is a proposed workaround by user machitgarha:

A solution would be defining a new private method and define as many variables as needed, as static ones inside it. Then, check if one of them is set, and if it is not, set all of them and return (or yield) them. This way, the variables will be declared only once, even if you have ten providers or whatever. Besides, you can use it in setUpBeforeClass() or setUp().

See it in an example:

private static function getData() {
    static $data, $anotherData;

    if (!isset($data)) {
        $data = new TestClass();
        $anotherData = [];
    }

    return [
        $data,
        // Or: clone $data
        $anotherData,
    ]; }

public static function setUpBeforeClass() {
    list(self::$sampleJson, self::$sampleData) = self::getData();  }

public function sampleProvider() {
    $data = self::getData();

    return [
        $data
    ]; }
Sign up to request clarification or add additional context in comments.

Comments

0

DataProviders are called before Setup. I see two possibilities if the state variable is to be set dynamically. One has to store the values outside the test class.

  1. Use Zend_Registry. Of course, you can also write your own object (singleton) for storing values.
use PHPUnit\Framework\TestCase;

class DataProviderSetupTest extends TestCase
{
    protected function setUp(): void
    {
        \Zend_Registry::set('state', 'Thank you for the fish!');
    }

    /**
     * @dataProvider statefulDataProvider
     */
    public function testDataProviderWithStateDependency(\Zend_Registry $expected): void
    {
        $actual = 'Thank you for the fish!'; // generated by some service under test
        $this->assertSame($expected::get('state'), $actual);
    }

    public function statefulDataProvider(): \Generator
    {
        yield 'member not initialized' => [\Zend_Registry::getInstance()];
    }
}
  1. The dirty workaround is to use global variables. You have to consider whether you can live with it in tests. With an object, you can simply transport several variables.
use PHPUnit\Framework\TestCase;

$transportObject = new class {
    public string $state;
};

class DataProviderSetupTest extends TestCase
{
    protected function setUp(): void
    {
        global $transportObject;
        $transportObject->state = 'Thank you for the fish!';
    }

    /**
     * @dataProvider statefulDataProvider
     */
    public function testDataProviderWithStateDependency(object $expected): void
    {
        $actual = 'Thank you for the fish!'; // generated by some service under test
        $this->assertSame($expected->state, $actual);
    }

    public function statefulDataProvider(): \Generator
    {
        global $transportObject;
        yield 'member not initialized' => [$transportObject];
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.