👾

[備忘録]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.phptimzezone="UTC"timzezone="Asia/Tokyo" に変更した
  • dataProviderでCarbonを生成し日時やタイムゾーンをチェックするphpunitテストでエラーが発生したので調査した

経緯/調査の詳細

  1. サンプルとして下記のようなテストコードを用意
    config/app.phptimzezone="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'.
  1. test_carbon 内で $checkDate をdumpで確認
^ Illuminate\Support\Carbon @1746144000^ {#333
~省略~
  date: 2025-05-01 00:00:00.0 UTC (+00:00)
}

$checkDateのタイムゾーンがUTCのままだった

  1. 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)
}
  1. 思ってもない実行順だったので、 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メソッドより先に呼び出されていることがわかった

  1. もう少し深ぼってみた。
    config/app.php が読み込まれるタイミングがどの辺りなのか Tests/TestCase.php を追ってみたところ、CreatesApplication Traitクラスを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を使用する際には頭の片隅にでも覚えておくとよさそうです。

EGSTOCK,Inc.

Discussion