使用 Factory 和 Worksome 来测试 Laravel 表单验证
一两个月前,我正忙于为 Laravel 应用编写测试。更具体地说,我正在测试一个 Laravel post 请求…
- 它允许用户注册
- 它需要一个有效电子邮箱地址
- 它不允许用户注册两次
- 它允许用户填写地址详细信息
您明白了。我为这个注册创建的测试越多,我越沮丧。为什么?因为我不得不 一遍又一遍地 重复自己。你看,任何值得接收的请求都会得到验证。 因此,即使我们的测试只处理请求中的一小部分字段,我们必须包括 所有!
让我们以 「它允许用户填写地址详细信息」 为例。我实际上可能对 4 个字段感兴趣:address_line_1
、address_line_2
、city
和 postcode
。但是,由于验证,我必须通过 email
、first_name
、last_name
、telephone
、accepts_terms
和其他所有必填字段。
it('它允许用户填写地址详细信息', function () {
$this->post('/signup', [
'first_name' => 'Luke', // 不用关心
'last_name' => 'Downing', // 不是我正在测试的
'email' => 'foo@bar.com', // Sigh
'telephone' => '01234567890', // 好吧,我现在很生气
'accepts_terms' => true, // 我们到了么?
'address_line_1' => '1 Test Street', // 最后!
'address_line_2' => 'Test Area',
'city' => 'Testerfield',
'postcode' => 'T35T 1NG',
]);
expect(User::latest()->first())
->address_line_1->toBe('1 Test Street')
->address_line_2->toBe('Test Area')
->city->toBe('Testerfield')
->postcode->toBe('T35T 1NG');
});
随着我们添加更多测试,这个问题只会变得更加令人沮丧。这是一个真正的问题,因为它实际上可能导致根本不能编写测试!此外,设想 6 个月后向表单添加一个新的必填字段。我们将不得不更新所有这些测试,并添加更多不会增加测试价值的数据,以使他们通过。
这让我思考。你看,我们以前也有过类似的 eloquent 模型的问题。但我们使用模型工厂解决了这个问题:类将为您完成所有繁重的工作,因此您只需在测试中编写 User::factory()->create()
代码。为什么不将同样方法应用于请求?
it('它允许用户填写地址详细信息', function () {
SignupRequest::fake();
$this->post('/signup', [
'address_line_1' => '1 Test Street',
'address_line_2' => 'Test Area',
'city' => 'Testerfield',
'postcode' => 'T35T 1NG',
]);
expect(User::latest()->first())
->address_line_1->toBe('1 Test Street')
->address_line_2->toBe('Test Area')
->city->toBe('Testerfield')
->postcode->toBe('T35T 1NG');
});
那太好了。它更短,更易于编写,更易于维护,并且无需过多的细节的即可传达了测试的目的。完美!
因此,事不宜迟,我很高兴向大家介绍 Request Factories by Worksome!
设置我们的 request factory
该包附带一个 artisan 命令,可以轻松为您的项目生成新工厂。您可以传递所需的工厂名称,或 form request 的完全限定类名 (FQCN)。
需要注意的是,表单请求是完全可选的。请求工厂也适用于标准请求。
# 使用自定义名称
php artisan make:request-factory SignupRequestFactory
# 使用表单 request FQCN
php artisan make:request-factory "App\Http\Requests\SignupRequest"
默认情况下,您的工厂将位于 tests/RequestFactories
下。当您打开新的请求工厂时,您会注意到一个返回数组的 definition
方法。就像模型工厂一样,这是我们在伪造请求时,定义我们想要呈现的任何属性的地方。
我的建议是你只在定义方法中包含完成一个有效请求的最少属性数量,并根据需要使用方法来装饰你的工厂。
class SignupRequestFactory extends RequestFactory
{
public function definition(): array
{
return [
'email' => $this->faker->safeEmail,
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'telephone' => '01234567890',
'accepts_terms' => true,
];
}
}
请注意,请求工厂有一个 $faker
属性,我们可以访问它来创建随机数据,而不是硬编码值。定义了必填字段后,我们现在可以伪造请求中的数据。
it('它允许用户填写地址详细信息', function () {
SignupRequestFactory::new()->fake();
$this->post('/signup', [
'address_line_1' => '1 Test Street',
'address_line_2' => 'Test Area',
'city' => 'Testerfield',
'postcode' => 'T35T 1NG',
]);
expect(User::latest()->first())
->address_line_1->toBe('1 Test Street')
->address_line_2->toBe('Test Area')
->city->toBe('Testerfield')
->postcode->toBe('T35T 1NG');
});
Eloquent 模型工厂的一个很酷的特性 (尽管可能会引起分歧) 是能够直接从模型创建新的工厂实例:User::factory()
。我也想把这种魔力带到请求工厂中,所以有一个可用的 Worksome\RequestFactories\Concerns\HasFactory
trait,如果你愿意的话,可以添加到您的表单请求中。添加后,直接从表单请求中伪造:
it('它允许用户填写地址详细信息', function () {
SignupRequest::fake();
$this->post('/signup', [
'address_line_1' => '1 Test Street',
'address_line_2' => 'Test Area',
'city' => 'Testerfield',
'postcode' => 'T35T 1NG',
]);
expect(User::latest()->first())
->address_line_1->toBe('1 Test Street')
->address_line_2->toBe('Test Area')
->city->toBe('Testerfield')
->postcode->toBe('T35T 1NG');
});
当然,这是完全可选的,只有在您的应用程序中使用表单请求时,才真正有意义。但如果这是你的风格,那就太好了。
为我们的工厂添加方法
工厂的一大优点是能够添加转换工厂状态的方法。例如,假设我们有一个邮政编码格式测试。我们需要提供其他地址字段来创建有效的请求,那么为什么不在我们的工厂中添加一个withAddress()
方法呢?
class SignupRequestFactory extends RequestFactory
{
public function definition(): array
{
return [
'email' => $this->faker->safeEmail,
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'telephone' => '01234567890',
'accepts_terms' => true,
];
}
public function withAddress(): self
{
return $this->state([
'address_line_1' => '1 Test Street',
'address_line_2' => 'Test Area',
'city' => 'Testerfield',
'postcode' => 'T35T 1NG',
]);
}
}
你会注意到 state
方法,类似于 Laravel 的模型工厂。我们试图保持 APIs 非常相似,因此会立即感到熟悉。现在,让我们开始编写测试:
it('正确设置邮政编码格式', function () {
SignupRequestFactory::new()->withAddress()->fake();
$this->post('/signup', ['postcode' => 'T3 5T1N G']);
expect(User::latest()->first()->postcode)->toBe('T35T 1NG')
});
那有多好? 我们测试保持其重点;我们只需要提供我们实际测试的字段。 另外,请注意,您传递给 post
(或任何 Laravel 请求测试方法),都将覆盖工厂中定义的数据。
其他好东西
在这篇文章中,我们实际上只是触及了请求工厂可能实现的表面。这里是一些其它可用的好东西的快速展示。
从请求中省略字段
it('需要电子邮件', function () {
SignupRequestFactory::new()->without(['email'])->fake();
$this->post('/signup')->assertInvalid(['email']);
});
为共享字段使用嵌套工厂
class MailingRequestFactory extends RequestFactory
{
public function definition(): array
{
return [
'email' => $this->faker->safeEmail,
'address' => AddressRequestFactory::new(),
];
}
}
对延迟定义的属性使用闭包
class MailingRequestFactory extends RequestFactory
{
public function definition(): array
{
return [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => fn ($attrs) => "{$attrs['first_name']}.{$attr['last_name']}@foo.com",
];
}
}
在工厂使用 create
如果您不想再全局范围内伪造请求或更喜欢接近模型工厂的语法,您可以在请求工厂上调用 create
以返回输入数组,然后可以将其传递给 post
或其它请求方法。
it('需要电子邮件', function () {
$data = SignupRequestFactory::new()->create();
$this->post('/signup', $data)->assertInvalid(['email']);
});
在 Pest PHP 中使用 fakeRequest
助手
如果您使用 Pest PHP 进行测试,我们提供一个非常棒的 fakeRequest
助手,如果您愿意,可以链接到测试的末尾。
it('正确设置邮政编码格式', function () {
$this->post('/signup', ['postcode' => 'T3 5T1N G']);
expect(User::latest()->first()->postcode)->toBe('T35T 1NG')
})
->fakeRequest(SignupRequestFactory::class)
->withAddress();
结束
有关您可以使用请求工厂做的所有事情的更多详细信息,请务必查看 详情文档!
我认为请求工厂在使用测试请求更易于编写、更易于维护并为每项测试提供清晰目的方面具有巨大潜力!
我期待着您在项目中使用它们! 一定要在 Twitter 上联系,让我知道你的想法。
保重!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
it 是什么意思
我一般通过数据提供器,在一个 test 测试接口的所有需测试功能,这个包很棒,可以方便分别测试一个接口的不同功能,不用放在一个 test 测完了