PHPunit10以降ではDataProvider関数はstaticが推奨になりました。
https://docs.phpunit.de/en/10.5/writing-tests-for-phpunit.html#data-providers
staticなので $this
を使ったテストクラスの状態を参照できなくなります。
そもそもDataProviderはテストケース実行前に呼ばれるため、setUpで作成したデータを参照できません。
実行順序の確認
class ExampleTest extends TestCase
{
private int $expectValue;
protected function setUp(): void
{
parent::setUp();
echo "setUp called\n";
$this->expectValue = random_int(0, 9);
}
#[DataProvider('data')]
public function test(int $actual): void
{
echo "testcase called\n";
$this->assertSame($this->expectValue, $actual);
}
public static function data()
{
echo "data called\n";
yield '1' => [1];
}
}
実行結果
.\vendor\bin\phpunit --filter=ExampleTest
data called
PHPUnit 10.5.15 by Sebastian Bergmann and contributors.
Runtime: PHP 8.2.4
Configuration: D:\dev\simutrans-portal\phpunit.xml
setUp called
testcase called
F 1 / 1 (100%)
Time: 00:00.108, Memory: 34.00 MB
There was 1 failure:
1) Tests\Unit\ExampleTest::test with data set #0 (1)
Failed asserting that 1 is identical to 5.
回避策
既存データ更新時のバリデーションなど状態に依存するケースを扱おうとする場合、以下のような回避策が可能です。
- DataProviderを使わず独立したテストケースにする
- クロージャを使用して$thisをバインドする
- クロージャを使用して$thisを引数として渡す
DataProviderを使わず独立したテストケースにする
無理に共通化することでテストコードの可読性が下がる可能性もあるため最初に検討するべき方法です。
class ExampleTest extends TestCase
{
private int $expectValue;
protected function setUp(): void
{
parent::setUp();
$this->expectValue = random_int(0, 9);
}
public function test(): void
{
// 実際はアサーション対象メソッドを通しての結果
$actual = $this->expectValue;
$this->assertSame($this->expectValue, $actual);
}
// その他DataProviderを使ったテストケース
}
クロージャを使用して$thisをバインドする
Closure::bind
を使用することでクロージャにテストクラスをバインドすることでアクセスできるようになります。
ただしIDEや静的解析では $this
を解析できず警告が出るのでおすすめできません。
class ExampleTest extends TestCase
{
private int $expectValue;
protected function setUp(): void
{
parent::setUp();
$this->expectValue = random_int(0, 9);
}
#[DataProvider('data')]
public function test(Closure $fn): void
{
$fn = Closure::bind($fn, $this);
$actual = $fn();
$this->assertSame($this->expectValue, $actual);
}
public static function data()
{
yield '1' => [fn () => $this->expectValue];
}
}
クロージャを使用して$thisを引数として渡す
クロージャ実行時の引数として $this
を渡すことで型ヒントや静的解析の支援も受けることができます。
class ExampleTest extends TestCase
{
private int $expectValue;
protected function setUp(): void
{
parent::setUp();
$this->expectValue = random_int(0, 9);
}
#[DataProvider('data')]
public function test(Closure $fn): void
{
$actual = $fn($this);
$this->assertSame($this->expectValue, $actual);
}
public static function data()
{
yield '1' => [fn (self $self) => $self->expectValue];
}
}
使用例
LaravelのFormRequestクラスでユーザー登録時のバリデーション条件テスト。
class StoreUserRequestTest extends TestCase
{
private User $user;
protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
}
#[DataProvider('dataValidation')]
public function test(Closure $setup, string $errorKey): void
{
$data = $setup($this);
$request = new StoreUserRequest($data);
$messageBag = Validator::make(
$data,
$request->rules() // ['email' => 'unique:users,email']
);
$this->assertArrayHasKey($errorKey, $messageBag->toArray());
}
public static function dataValidation(): \Generator
{
yield '登録済みのメールアドレス' => [
fn (self $self): array => ['email' => $self->user->email],
'email'
];
// その他のテストケース
}
}