在PHPUnit提供程序中使用工厂失败

Ala*_*bak 5 php phpunit laravel laravel-5.3

我正在尝试使用模型工厂在数据提供程序中创建模型。如果我在设置方法中或直接在测试中使用工厂,则可以使用,但是如果我尝试在数据提供程序中使用它,则会出现错误:

1)警告

为MyClassTest :: testSomeMethod指定的数据提供者无效。

无法找到名称为[default] [App \ Model \ User]的工厂。

工厂定义:

/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(\App\Model\User::class, function (Faker\Generator $faker) {
    static $password;

    return [
        'id' => $faker->unique()->numberBetween(1, 10000),
        'email_address' => $faker->unique()->safeEmail,
        'remember_token' => str_random(10),
        'password' => $password ?: $password = bcrypt('secret'),
    ];
});

$factory->state(\App\Model\User::class, 'guest', function ($faker) {
    return [
        'role_id' => 9999,
    ];
});
Run Code Online (Sandbox Code Playgroud)

我给工厂打电话:

factory(\App\Model\User::class)->states('guest')->make();
Run Code Online (Sandbox Code Playgroud)

是Laravel的错误,还是我在这里错过了一些东西?

编辑:

经过一些调试之后,我发现工厂定义没有在数据提供者调用之前加载,而是在调用setUp()方法时(即在数据提供者调用之后发生)调用了(即定义了)工厂定义,因此找不到工厂在数据提供者中。

因此在我看来,不可能在数据提供程序中使用工厂(或测试类中的任何静态方法)。否则我应该做一些事情来在我的数据提供者方法中定义工厂!

Ala*_*bak 9

我在另一个问题(由同一问题引起)中找到了答案

因此,可以根据此答案通过调用$this->createApplication();$this->refreshApplication();在数据提供者方法中解决此问题,或根据此答案在构造函数中调用它来解决此问题

该代码将如下所示

public function dataProvider() {
    $this->refreshApplication(); // or $this->createApplication();
    $user = factory(\App\Model\User::class)->states('guest')->make();

    // ...
    // another code to generate data ....
    // ...

    return $someFakeData;
}
Run Code Online (Sandbox Code Playgroud)

我尝试过并工作了,尽管我觉得这是一种解决方法,而不是事情应该如何工作,但任何“更清洁”的建议都会受到赞赏。


Dan*_*her 8

可以在您的数据提供程序中使用工厂,并且仍然可以使用数据库事务!

今天这让我很沮丧,我找到了一个受这个答案启发的解决方案,我找到了这个答案

它不漂亮,但它是:

更新我也把它变成了一篇博客文章,它更详细一点:https : //technicallyfletch.com/how-to-use-laravel-factories-inside-a-data-provider/

首先,我修改了使用提供程序的方式。我期望一个函数,我可以从中解构参数,而不是像往常一样期待参数列表。这就是将工厂的执行推迟到我进入我的测试用例之后的原因。

    /**
     * @test
     * @dataProvider validationProvider
     */
    public function it_validates_payload($getData)
    {
        // data provider now returns a function which we can invoke to
        // destructure our arguments here.
        [$ruleName, $payload] = $getData();

        $this->post(route('participants.store'), $payload)
            ->assertSessionHasErrors($ruleName);
    }
Run Code Online (Sandbox Code Playgroud)

我的提供者现在变成了这样:

    public function validationProvider()
    {
        return [
            'it fails if participant_type_id does not exist' => [
                function () {
                    return [
                        'participant_type_id',
                        array_merge($this->getValidData(), ['participant_type_id' => null])
                    ];
                }
            ],
            'it fails if first_name does not exist' => [
                function () {
                    return [
                        'first_name',
                        array_merge($this->getValidData(), ['first_name' => null])
                    ];
                }
            ],
            'it fails if last_name does not exist' => [
                function () {
                    return [
                        'last_name',
                        array_merge($this->getValidData(), ['last_name' => null])
                    ];
                }
            ],
            'it fails if email is not unique' => [
                function () {
                    $existingEmail = factory(Participant::class)->create([
                        'email' => 'pbeasly@dundermifflin.com'
                    ])->email;
                    return [
                        'email',
                        array_merge($this->getValidData(), ['email' => $existingEmail])
                    ];
                }
            ],
        ];
    }
Run Code Online (Sandbox Code Playgroud)

然后这有点离题,但它很好地说明了你可以推迟工厂。该getValidData()方法仅返回一组有效数据,因此每个测试一次仅断言一个验证错误。它也使用工厂:

    private function getValidData()
    {
        return [
            'practice_id' => factory(Practice::class)->create()->id,
            'participant_type_id' => 1,
            'first_name' => 'John',
            'last_name' => 'Doe',
        ];
    }
Run Code Online (Sandbox Code Playgroud)

有些事情仍然困扰着我

  1. 只是看起来很邋遢。数据提供者已经很难可读了,这只会让情况变得更糟。虽然您可以抽象一个实用程序来帮助清理它。
  2. 在我的示例中,我有一个为每个场景进行的数据库调用,因为它在每次执行提供者返回的闭包时都会运行......哎呀!我不确定解决这个问题的最佳方法是什么,但一种方法是在类本身的构造函数中设置一些状态。一旦id被供应商的第一次执行后创建的,你可以拉从国家的ID,而不是使该数据库每次新的呼叫。

否则,当您需要它时,它是一个有效的解决方案,我希望它可以帮助其他人,如果他们碰巧找到了这个!