リモート開発メインのソフトウェア開発企業のエンジニアブログです

Laravel のテストで data provider の中で faker を使うとエラーになる

長いタイトルになりましたが、タイトル通りです。エラーメッセージは InvalidArgumentException: Unknown format "name" です。

環境

Laravel 9 で確認しましたが、10, 11 でも同様だと思います。

現象

エラーが起きるコード

実際のコードを少し簡略化して載せます。

テストケースはこんな感じです。

// テストケース
use Tests\TestCase;

class AccountTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        // ここで本テストケース固有の初期化処理を実行するが、詳細は省略
    }

    // 問題となる data provider
    public function provider_user()
    {
        return [
            'User without foo' => [$this->getCustomer()],
            'User with foo' => [$this->getCustomerWithFoo()],
        ];
    }

    // テスト内で使われるユーティリティメソッド1
    private function getCustomer($overrideUser = [])
    {
        // ファクトリーを呼び出し
        return Customer::factory($overrideUser)
            ->create();
    }

    // テスト内で使われるユーティリティメソッド2
    private function getCustomerWithFoo($overrideUser = [], $overrideFoo = [])
    {
        // ファクトリーを呼び出し
        return Customer::factory($overrideUser)
            ->has(Foo::factory($overrideFoo)->count(1))
            ->create();
    }

    /**
     * テストケース
     * @dataProvider provider_user
     * @return void
     */
    public function test_some_behavior($user)
    {
        $response = $this->actingAs($user)->get('/some/page');
        $response->assertStatus(200);
    }
}

ファクトリーは以下の通りです。

use Illuminate\Database\Eloquent\Factories\Factory;

class CustomerFactory extends Factory
{
    public function definition()
    {
        // faker を使ってます。
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

エラーの内容

こんな感じのエラーが出ます。

  • PHPUnit\Framework\ErrorTestCase > error
   PHPUnit\Framework\Error

  The data provider specified for Tests\Feature\Customer\AccountTest::test_some_behavior is invalid.
InvalidArgumentException: Unknown format "name"
/var/www/html/vendor/fakerphp/faker/src/Faker/Generator.php:731
/var/www/html/vendor/fakerphp/faker/src/Faker/Generator.php:696
/var/www/html/vendor/fakerphp/faker/src/Faker/Generator.php:961
/var/www/html/database/factories/CustomerFactory.php:19
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:429
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:408
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:392
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php:155
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:397
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:365
/var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php:265
/var/www/html/tests/Feature/Customer/AccountTest.php:43

  at vendor/phpunit/phpunit/phpunit:98
     94▕ unset($options);                                                                                                                           95▕
     96▕ require PHPUNIT_COMPOSER_INSTALL;                                                                                                          97▕
  ➜  98▕ PHPUnit\TextUI\Command::main();
     99▕

Moba Pro

どうすれば良いか

テストケースを以下のように修正すれば良いです。変更点のみを記載します。

    // data provider
    public function provider_customer_creation_method_name()
    {
        // $this->getCustomer(), getCustomerWithFoo() を呼び出してその値を返すのでは無く
        // メソッド名を返すように変更。
        return [
            'User without foo' => ['getCustomer'],
            'User with foo' => ['getCustomerWithFoo'],
        ];
    }

    /**
     * テストケース
     * @dataProvider provider_customer_creation_method_name
     * @return void
     */
    public function test_some_behavior($method)
    {
        $user = call_user_func([$this, $method]); // 変更点

        $response = $this->actingAs($user)->get('/customer/account/show');
        $response->assertStatus(200);
    }

同僚に本記事をレビューしてもらった際、以下のように closure を使った方法を教えてもらいました。こちらでも問題ありません。

    // data provider
    public function provider_customer_creation_closure()
    {
        // $this->getCustomer(), getCustomerWithFoo() を直接呼び出しすのでは無く
        // closure 内で呼び出すように変更。
        return [
            'User without foo' => [fn() => static::getCustomer()],
            'User with foo' => [fn() => static::getCustomerWithFoo()],
        ];
    }

    /**
     * テストケース
     * @dataProvider provider_customer_creation_closure
     * @return void
     */
    public function test_some_behavior($createUserClosure)
    {
        $user = $createUserClosure(); // 変更点

        $response = $this->actingAs($user)->get('/customer/account/show');
        $response->assertStatus(200);
    }

どちらも出来る事としては変わらないと思います。

解説

data provider は、テストケースの初期化・実行より前に評価されます。 Testing\TestCase::setUp の中で faker の初期化も含めた各種初期化処理が行われるため、それより前に faker を使おうとするとエラーとなります。

それを回避するためには、data provider の中で faker を使ったファクトリーを呼び出すのでは無く、テストケース内で呼び出すようにします。

ちなみに、PHP に遅延評価の仕組みがあればもっと簡単に解決できると思います。

まとめ

Laravel のテストで data provider の中で faker を使うとエラーになります。data provider の中で faker を使うケースというのは、data provider の中で faker を使ったファクトリーを呼び出す場合が主だと思います。それを回避するためには、data provider の中ではなくテストケースの中でファクトリーを呼び出すようにします。

参考

testing – Lumen 8 – Using Faker in tests makes InvalidArgumentException: Unknown format “name” – Stack Overflow

← 前の投稿

M1 MacでDockerを使用するための覚書

次の投稿 →

Rails 8.0 が出たのでアップグレードした

コメントを残す