我正在尝试使用模型工厂在数据提供程序中制作模型。如果我在设置方法或直接测试中使用工厂,它会起作用,但如果我尝试在数据提供程序中使用它,则会收到错误:
1) 警告
为 MyClassTest::testSomeMethod 指定的数据提供程序无效。
无法找到名称为[默认][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,
];
});
我给工厂打电话:
factory(\App\Model\User::class)->states('guest')->make();
这是 Laravel 的一个错误还是我在这里遗漏了一些东西?
编辑:
经过一些调试,我发现工厂定义在数据提供程序调用之前没有加载,它们在调用 setUp() 方法时被调用(即定义) - 这发生在数据提供程序调用之后 - 因此它找不到数据提供者中的工厂。
所以在我看来,不可能在数据提供程序中使用工厂(或测试类中的任何静态方法)。 或者我应该做一些事情来在我的数据提供程序方法中定义工厂!
我在另一个问题中找到了答案(由同一问题引起)
因此,这个问题可以通过根据
这个答案在数据提供者方法中调用
$this->createApplication();
或$this->refreshApplication();
来解决,
或者根据 this 答案 在构造函数中调用它
代码看起来像这样
public function dataProvider() {
$this->refreshApplication(); // or $this->createApplication();
$user = factory(\App\Model\User::class)->states('guest')->make();
// ...
// another code to generate data ....
// ...
return $someFakeData;
}
我尝试过并成功了,尽管我觉得这是一种解决方法,而不是事情应该如何运作,但任何“更干净”的建议将不胜感激。
可以在数据提供者内部使用工厂并且仍然可以进行数据库事务!
今天这让我很沮丧,我找到了一个受这个答案启发的解决方案,这要归功于这个答案
它不漂亮,但它就是:
更新我还将其变成了一篇博客文章,其中更详细一些: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);
}
我的提供商现在变成了这样:
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' => '[email protected]'
])->email;
return [
'email',
array_merge($this->getValidData(), ['email' => $existingEmail])
];
}
],
];
}
虽然这有点离题,但它很好地说明了你可以推迟工厂的建设。
getValidData()
方法仅返回一组有效数据,因此每个测试一次仅断言一个验证错误。它也使用工厂:
private function getValidData()
{
return [
'practice_id' => factory(Practice::class)->create()->id,
'participant_type_id' => 1,
'first_name' => 'John',
'last_name' => 'Doe',
];
}
有些事情仍然困扰着我
id
后,您可以从状态中提取 id,而不是每次都对数据库进行新的调用。否则,当您需要这个时,这是一个有效的解决方案,我希望它可以帮助其他人(如果他们碰巧找到这个)!
或者,您可以使用Pest,其中工厂仅在数据提供商中工作。
it('can just use a factory in a data provider', function(YourModel $yourModel) {
expect($yourModel)->toBeInstanceOf(YourModel::class);
})->with([
[fn() => YourModel::factory()->create()],
]);
@丹·弗莱彻
绝妙的解决方案(这个https://stackoverflow.com/a/61807494)!
您必须从数据提供商处调用
createApplication
的另一种解决方案对我来说不起作用(除了效率低下之外)。
我认为你可以通过使用“速记闭包”来使语法稍微更容易接受,可以选择与
yield
结合使用,例如
yield [ fn ($user) => $this->getData($user, 'subject', null, 'Subject is required.') ];
闭包的
$user
参数将是您通过 Laravel Factory 创建的模型对象(这就是最终的重点)。
做
dd(\App\Model\User::class);
查看它是否返回正确的完全限定类名,如果没有,那么这可能是您的问题。