👾
[備忘録]phpunitのdataProviderメソッドはsetupメソッドよりも先に呼び出されるっぽい
この記事について
- phpunitのテストコードを修正した際に遭遇した現象について、下手したらハマりそうだなと思ったので備忘録にしました
 
使用マシン/ツール
- macOS Sonoma ver14.7.5
 - Docker Desktop ver4.23.0
 - Laravel Framework ver10.32.1
 - phpunit/phpunit ver10.4.2
 
経緯
- 業務でLaravelのタイムゾーンをJSTに変更することになったので、 
config/app.phpのtimzezone="UTC"をtimzezone="Asia/Tokyo"に変更した - dataProviderでCarbonを生成し日時やタイムゾーンをチェックするphpunitテストでエラーが発生したので調査した
 
経緯/調査の詳細
- サンプルとして下記のようなテストコードを用意
※config/app.phpでtimzezone="Asia/Tokyo"にしていることを前提にしています 
use Illuminate\Support\Carbon;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class ExampleTest extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();
    }
    #[DataProvider('parameter')]
    public function test_carbon(Carbon $checkDate): void
    {
        $actual = $checkDate->timezone;
        $this->assertEquals('Asia/Tokyo', $actual);
    }
    public static function parameter(): array
    {
        return [
            'タイムゾーンがAsia/Tokyo' => [Carbon::make('2025-05-01 00:00:00')]
        ];
    }
想定では問題なくテストが通るはずだったが、下記エラーが発生
  Failed asserting that Carbon\CarbonTimeZone Object &0000000000002ae30000000000000000 (
      'timezone_type' => 3
      'timezone' => 'UTC'
  ) matches expected 'Asia/Tokyo'.
- 
test_carbon内で$checkDateをdumpで確認 
^ Illuminate\Support\Carbon @1746144000^ {#333
~省略~
  date: 2025-05-01 00:00:00.0 UTC (+00:00)
}
$checkDateのタイムゾーンがUTCのままだった
- dataProviderで生成したCarbonがおかしいことがわかったので、今度は 
parameterメソッド内でdumpを仕込んでみたところ、test_carbonメソッド よりも先にparameterメソッド が呼び出されている ことがわかった 
コード
    #[DataProvider('parameter')]
    public function test_carbon(Carbon $checkDate): void
    {
        dump($checkDate);
~省略~
    }
    public static function parameter(): array
    {
        dump('parameter:' . now()->timezone);
~省略~
    }
実行結果
^ "parameter:UTC"
^ Illuminate\Support\Carbon @1746144000^ {#333
~省略~
  date: 2025-05-01 00:00:00.0 UTC (+00:00)
}
- 思ってもない実行順だったので、 
setup()メソッドにもdumpを仕込んで実行順を確認してみた 
コード
    public function setUp(): void
    {
        dump('setup');
        parent::setUp();
    }
    #[DataProvider('parameter')]
    public function test_carbon(Carbon $checkDate): void
    {
        dump('test_carbon');
~省略~
    }
    public static function parameter(): array
    {
        dump('parameter');
~省略~
    }
実行結果
^ "parameter"
^ "setup"
^ "test_carbon"
parameterメソッドがsetupメソッドより先に呼び出されていることがわかった
- もう少し深ぼってみた。
config/app.phpが読み込まれるタイミングがどの辺りなのかTests/TestCase.phpを追ってみたところ、CreatesApplicationTraitクラスをuseしており、中のcreateApplicationメソッドではbootstrap/app.phpを読み込んでいた。
試しに下記のようにコードに追記して実行してみた 
    public function createApplication(): Application
    {
        $app = require __DIR__.'/../bootstrap/app.php';
        dump(config('app.timezone')); // ①
        $app->make(Kernel::class)->bootstrap();
        dump(config('app.timezone')); // ②
        return $app;
    }
すると下記エラーが発生
^ "parameter"
^ "setup"
   FAILED  Tests\Unit\ExampleTest > carbon with data set "check dateが5/1 0時"                                         BindingResolutionException   
  Target class [config] does not exist.
①の段階ではbootstrapの読み込み前なのでエラーになってしまった。
①をコメントアウトして再実行すると、下記のようになった
^ "parameter"
^ "setup"
"Asia/Tokyo"  ← ②のdump
"test_carbon"
configが反映されるのは setup メソッドが実行されるタイミングと見てよさそう
対応について
今回はひとまず、dataProvider側で生成しているCarbon::makeにタイムゾーンを指定することで対応しました
Carbon::make('2025-05-01 00:00:00')->setTimezone('Asia/Tokyo')
まとめ
今回はたまたまdumpをこまめに仕込んでいたためすぐに気づけましたが、他にもconfigの設定が遅延して反映されることはありそうなので、dataProviderを使用する際には頭の片隅にでも覚えておくとよさそうです。
Discussion