Dov*_*ski 18 php unit-testing request laravel laravel-5.2
我正在尝试对各种自定义FormRequest输入进行单元测试.我找到了解决方案:
建议使用该$this->call(…)方法并response使用期望值断言(链接到答案).这是过度的,因为它直接依赖于路由和控制器.
泰勒的测试,从Laravel框架 中发现的 tests/Foundation/FoundationFormRequestTest.php.那里有很多嘲弄和开销.
我正在寻找一种解决方案,我可以根据规则对各个字段输入进行单元测试(独立于同一请求中的其他字段).
SampleRequest示例:
public function rules()
{
return [
'first_name' => 'required|between:2,50|alpha',
'last_name' => 'required|between:2,50|alpha',
'email' => 'required|email|unique:users,email',
'username' => 'required|between:6,50|alpha_num|unique:users,username',
'password' => 'required|between:8,50|alpha_num|confirmed',
];
}
Run Code Online (Sandbox Code Playgroud)
期望的测试:
public function testFirstNameField()
{
// assertFalse, required
// ...
// assertTrue, required
// ...
// assertFalse, between
// ...
}
public function testLastNameField()
{
// ...
}
Run Code Online (Sandbox Code Playgroud)
我如何单独测试(断言)每个字段的每个验证规则?
Dov*_*ski 20
我在Laracast上找到了一个很好的解决方案,并添加了一些定制功能.
代码
public function setUp()
{
parent::setUp();
$this->rules = (new UserStoreRequest())->rules();
$this->validator = $this->app['validator'];
}
/** @test */
public function valid_first_name()
{
$this->assertTrue($this->validateField('first_name', 'jon'));
$this->assertTrue($this->validateField('first_name', 'jo'));
$this->assertFalse($this->validateField('first_name', 'j'));
$this->assertFalse($this->validateField('first_name', ''));
$this->assertFalse($this->validateField('first_name', '1'));
$this->assertFalse($this->validateField('first_name', 'jon1'));
}
protected function getFieldValidator($field, $value)
{
return $this->validator->make(
[$field => $value],
[$field => $this->rules[$field]]
);
}
protected function validateField($field, $value)
{
return $this->getFieldValidator($field, $value)->passes();
}
Run Code Online (Sandbox Code Playgroud)
更新
有一个针对同一问题的e2e方法.您可以将要检查的数据POST到相关路由,然后查看响应是否包含会话错误.
$response = $this->json('POST',
'/route_in_question',
['first_name' => 'S']
);
$response->assertSessionHasErrors(['first_name');
Run Code Online (Sandbox Code Playgroud)
mat*_*iti 12
我看到这个问题有很多观点和误解,所以我会添加我的沙子来帮助仍然有疑问的人。
首先,记住永远不要测试框架,如果您最终做了与其他答案类似的事情(构建或绑定框架核心的模拟(忽略外观),那么您就在做与测试相关的错误)。
因此,如果您想测试控制器,最好的方法是:对其进行功能测试。永远不要对它进行单元测试,不仅对它进行单元测试很麻烦(用数据创建请求,也许有特殊要求),而且还要实例化控制器(有时不是并且new HomeController完成了......)。
他们解决作者问题的方法是像这样进行功能测试(记住,这是一个例子,有很多方法):
假设我们有这样的规则:
public function rules()
{
return [
'name' => ['required', 'min:3'],
'username' => ['required', 'min:3', 'unique:users'],
];
}
Run Code Online (Sandbox Code Playgroud)
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class HomeControllerTest extends TestCase
{
use RefreshDatabase;
/*
* @dataProvider invalid_fields
*/
public function test_fields_rules($field, $value, $error)
{
// Create fake user already existing for 'unique' rule
User::factory()->create(['username' => 'known_username']);
$response = $this->post('/test', [$field => $value]);
$response->assertSessionHasErrors([$field => $error]);
}
public function invalid_fields()
{
return [
'Null name' => ['name', null, 'The name field is required.'],
'Empty name' => ['name', '', 'The name field is required.'],
'Short name' => ['name', 'ab', 'The name must be at least 3 characters.'],
'Null username' => ['username', null, 'The username field is required.'],
'Empty username' => ['username', '', 'The username field is required.'],
'Short username' => ['username', 'ab', 'The username must be at least 3 characters.'],
'Unique username' => ['username', 'known_username', 'The username has already been taken.'],
];
}
}
Run Code Online (Sandbox Code Playgroud)
就是这样......这就是进行此类测试的方式......不需要实例化/模拟和绑定任何框架(Illuminate命名空间)类。
我也在利用 PHPUnit,我正在使用,data providers所以我不需要复制粘贴测试或创建一个受保护/私有方法,测试将调用该方法来“设置”任何内容...我重用测试,我只是更改输入(字段、值和预期错误)。
如果您需要测试 a 是否view正在显示,只需执行$response->assertViewIs('whatever.your.view');,您还可以传递第二个属性(但使用assertViewHas)来测试视图中是否有变量(以及所需的值)。同样,不需要实例化/模拟任何核心类......
考虑到这只是一个简单的例子,它可以做得更好一点(避免复制粘贴一些错误消息)。
最后一件重要的事情:如果您对此类事物进行单元测试,那么,如果您更改后面的完成方式,则必须更改您的单元测试(如果您已经模拟/实例化了核心类)。例如,也许您现在使用的是FormRequest,但后来您切换到其他验证方法,例如Validator直接验证或对其他服务的 API 调用,因此您甚至没有直接在代码中进行验证。如果您进行功能测试,则不必更改单元测试代码,因为它仍然会接收相同的输入并给出相同的输出,但如果它是单元测试,那么您将更改其工作方式。 .. 这就是我要说的“NO-NO”部分......
始终将测试视为:
因此,您应该将测试视为黑匣子。输入 -> 输出,不需要复制它的中间...你可以设置一些假货,但不能伪造所有内容或它的核心...你可以嘲笑它,但我希望你明白我的意思,在此刻...
朋友们,请好好进行单元测试,毕竟这里不光是rules你在测试,validationData和withValidator函数也可能在那里。
应该这样做:
<?php
namespace Tests\Unit;
use App\Http\Requests\AddressesRequest;
use App\Models\Country;
use Faker\Factory as FakerFactory;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use function app;
use function str_random;
class AddressesRequestTest extends TestCase
{
public function test_AddressesRequest_empty()
{
try {
//app(AddressesRequest::class);
$request = new AddressesRequest([]);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
//\Log::debug(print_r($ex->errors(), true));
$this->assertTrue(isset($ex));
$this->assertTrue(array_key_exists('the_address', $ex->errors()));
$this->assertTrue(array_key_exists('the_address.billing', $ex->errors()));
}
public function test_AddressesRequest_success_billing_only()
{
$faker = FakerFactory::create();
$param = [
'the_address' => [
'billing' => [
'zip' => $faker->postcode,
'phone' => $faker->phoneNumber,
'country_id' => $faker->numberBetween(1, Country::count()),
'state' => $faker->state,
'state_code' => str_random(2),
'city' => $faker->city,
'address' => $faker->buildingNumber . ' ' . $faker->streetName,
'suite' => $faker->secondaryAddress,
]
]
];
try {
//app(AddressesRequest::class);
$request = new AddressesRequest($param);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
$this->assertFalse(isset($ex));
}
}
Run Code Online (Sandbox Code Playgroud)