jcergolj/laravel-form-request-assertions

用于单元测试Laravel表单请求类的包

v1.12 2024-09-19 07:57 UTC

This package is auto-updated.

Last update: 2024-09-19 07:59:21 UTC


README

原因

Colin DeCarlo在Laracon online 21上就单元测试Laravel表单请求类进行了演讲。如果您还没有看过他的演讲,我建议您观看。他更喜欢将表单请求作为一个单元来测试,而不是作为功能测试。我也喜欢这种做法。

他请求Freek Van der Herten将他的gist代码转换为包。虽然我不是Freek;然而,我接受了挑战,并自己完成了它。所以这个包只是Colin的gist的一个包装,并且我添加了Jason的中的两个方法,用于断言控制器是否有表单请求。

安装

需要的PHP >=8.0

composer require --dev jcergolj/laravel-form-request-assertions

用法

控制器

<?php

namespace App\Http\Controllers;

use App\Http\Requests\CreatePostRequest;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function store(CreatePostRequest $request)
    {
        // ...
    }
}

web.php 路由

<?php

use App\Http\Controllers\PostController;

Route::post('posts', [PostController::class, 'store']);

请求

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreatePostRequest extends FormRequest
{
    public function authorize()
    {
	    return $this->user()->id === 1 && $this->post->id === 1;
    }

    function rules()
    {
        return ['email' => ['required', 'email']];
    }
}

将特性添加到单元测试中

在包安装后添加TestableFormRequest特性

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Jcergolj\FormRequestAssertions\TestableFormRequest;

class CreatePostRequestTest extends TestCase
{
    use TestableFormRequest;

    // ...
}

控制器是否有表单请求测试?

public function controller_has_form_request()
{
    $this->assertActionUsesFormRequest(PostController::class, 'store', CreatePostRequest::class);
}

或者

public function controller_has_form_request()
{
    $this->post(route('users.store'));

    $this->assertContainsFormRequest(CreateUserRequest::class);
}

测试失败的验证规则

public function email_is_required()
{
    $this->createFormRequest(CreatePostRequest::class)
        ->validate(['email' => ''])
        ->assertFails(['email' => 'required'])
	->assertHasMessage('The email field is required.', 'email');

    $this->createFormRequest(CreatePostRequest::class)
        ->validate(['password' => 'short'])
        ->assertFails(['password' => App\Rules\PasswordRule::class]); //custom password rule class
}

测试属性有规则

当处理更复杂的规则时,您可能需要将逻辑提取到专门的自定义规则类中。在这种情况下,您不想在RequestTest类中测试逻辑,而是想在专门的自定义规则测试类中测试。在这里,您只关心给定的属性是否有/包含自定义规则。

public function email_has_custom_rule_applied()
{
    $this->createFormRequest(CreatePostRequest::class)
        ->validate()
        ->assertHasRule('email', new CustomRule); // here we don't validate the rule, but just make sure rule is applied
}

测试断言规则子集未失败

在某些情况下,您可能不关心整个请求是否通过,只是那组验证规则没有失败。

public function test_email_is_not_required()
{
    /** Validation rules: ['email' => 'email|nullable'] */
    $this->createFormRequest(CreatePostRequest::class)
        ->validate([])
        ->assertRulesWithoutFailures(['email' => 'required']);
}

警告:这仅检查规则没有失败,它不检查规则是否最初实际应用!

测试表单请求

 /** @test */
function test_post_author_is_authorized()
{
    $author = User::factory()->make(['id' => 1]);
    $post = Post::factory()->make(['id' => 1]);

    $this->createFormRequest(CreatePostRequest::class)
        ->withParam('post', $post)
        ->actingAs($author)
        ->assertAuthorized();
}

测试数据准备

测试在FormRequestprepareForValidation方法中数据是如何准备的。

 /** @test */
function test_transforms_email_to_lowercase_before_validation()
{
    $this->createFormRequest(CreatePostRequest::class)
        ->onPreparedData(['email' => 'TeSt@ExAmPlE.cOm'], function (array $preparedData) {
            $this->assertEquals('test@example.com', $preparedData['email']);
        });
}

扩展

如果您需要额外的/自定义的断言,您可以轻松扩展\Jcergolj\FormRequestAssertions\TestFormRequest类。

  1. 创建一个新的类,例如:\Tests\Support\TestFormRequest,它扩展了\Jcergolj\FormRequestAssertions\TestFormRequest类。
    namespace Tests\Support;
    class TestFormRequest extends \Jcergolj\FormRequestAssertions\TestFormRequest
    {
      public function assertSomethingImportant()
      {
        // your assertions on `$this->request`
      }
    }
  2. 创建一个新的特性,例如:\Tests\Traits\TestableFormRequest,它使用了\Jcergolj\FormRequestAssertions\TestableFormRequest特性。
  3. 重写\Jcergolj\FormRequestAssertions\TestableFormRequest::createNewTestFormRequest方法,以便返回(1)中创建的类的实例。
    namespace Tests\Support;
    trait TestableFormRequest {
      use \Jcergolj\FormRequestAssertions\TestableFormRequest;
    
      protected function createNewTestFormRequest(FormRequest $request): TestFormRequest
      {
        return new \Tests\Support\TestFormRequest($request);
      }
    }
  4. 在您的测试类中使用您自定义的特性而不是\Jcergolj\FormRequestAssertions\TestableFormRequest

可用方法

createFormRequest(string $requestClass, $headers = [])
assertRouteUsesFormRequest(string $routeName, string $formRequest)
assertActionUsesFormRequest(string $controller, string $method, string $form_request)
validate(array $data)
by(Authenticatable $user = null)
actingAs(Authenticatable $user = null)
withParams(array $params)
withParam(string $param, $value)
assertAuthorized()
assertNotAuthorized()
assertPasses()
assertFails($expectedFailedRules = [])
assertHasMessage($message, $rule = null)
getFailedRules()

贡献者

非常感谢Colin和Jason。我从Colin的gist创建了一个包,并从Jason的包中复制了两个方法。