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.
Required 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)
{
// ...
}
}
<?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']];
}
}
After package installation add the TestableFormRequest
trait
<?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);
}
or
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('Email is required', 'required');
$this->createFormRequest(CreatePostRequest::class)
->validate(['password' => 'short'])
->assertFails(['password' => App\Rules\PasswordRule::class]); //custom password rule class
}
/** @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();
}
If you need additional/custom assertions, you can easily extend the \Jcergolj\FormRequestAssertions\TestFormRequest
class.
- 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` } }
- Create a new trait, for example:
\Tests\Traits\TestableFormRequest
using the\Jcergolj\FormRequestAssertions\TestableFormRequest
trait. - 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); } }
- Use your custom trait instead of
\Jcergolj\FormRequestAssertions\TestableFormRequest
on your test classes
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()
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.
Colin DeCarlo |
Jason McCreary |
Janez Cergolj |