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.
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.
Share Improve this question asked Feb 17 at 11:35 k0pernikusk0pernikus 66.7k77 gold badges242 silver badges361 bronze badges 2- Github issue on phpunit's project site – k0pernikus 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. – hakre Commented Feb 17 at 13:16
2 Answers
Reset to default 1This 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 ]; }
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.
- 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()];
}
}
- 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];
}
}