jcergolj/laravel-form-request-assertions

Package for unit test laravel form request classes

v1.9 2024-04-25 15:09 UTC

This package is auto-updated.

Last update: 2024-04-25 15:12:02 UTC


README

Why

Colin DeCarlo gave a talk on Laracon online 21 about unit testing Laravel form requests classes. If you haven't seen his talk, I recommend that you watch it. He prefers testing form requests as a unit and not as feature tests.I like this approach too.

He asked Freek Van der Herten to convert his gist code to package. Granted, I am not Freek; however, I accepted the challenge, and I did it myself. So this package is just a wrapper for Colin's gist, and I added two methods from Jason's package for asserting that controller has the form request.

Installation

Required PHP >=8.0

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

Usage

Controller

<?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 routes

<?php

use App\Http\Controllers\PostController;

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

Request

<?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']];
    }
}

Add the trait to a unit test

After package installation add the TestableFormRequest trait

<?php

namespace Tests\Unit;

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

class CreatePostRequestTest extends TestCase
{
    use TestableFormRequest;

    // ...
}

Does the controller have the form request test?

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

or

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

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

Test Validation Rules

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

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

Test Form Request

 /** @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();
}

Extending

If you need additional/custom assertions, you can easily extend the \Jcergolj\FormRequestAssertions\TestFormRequest class.

  1. Create a new class, for example: \Tests\Support\TestFormRequest extending the \Jcergolj\FormRequestAssertions\TestFormRequest class.
    namespace Tests\Support;
    class TestFormRequest extends \Jcergolj\FormRequestAssertions\TestFormRequest
    {
      public function assertSomethingImportant()
      {
        // your assertions on `$this->request`
      }
    }
  2. Create a new trait, for example: \Tests\Traits\TestableFormRequest using the \Jcergolj\FormRequestAssertions\TestableFormRequest trait.
  3. Overwrite the \Jcergolj\FormRequestAssertions\TestableFormRequest::createNewTestFormRequest method to return an instance of the class created in (1).
    namespace Tests\Support;
    trait TestableFormRequest {
      use \Jcergolj\FormRequestAssertions\TestableFormRequest;
    
      protected function createNewTestFormRequest(FormRequest $request): TestFormRequest
      {
        return new \Tests\Support\TestFormRequest($request);
      }
    }
  4. Use your custom trait instead of \Jcergolj\FormRequestAssertions\TestableFormRequest on your test classes

Available Methods

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()

Contributors

A huge thanks go to Colin and Jason. I created a package from Colin's gist and I copied two methods from Jason's package.

682860?v=4
Colin DeCarlo
161071?v=4
Jason McCreary
6940394?s=460&u=b4eaa035a3526a442d7d09dbf4d9d3ca63bfc1a5&v=4
Janez Cergolj