How to mock a Form Request Validation in Laravel

Learn how easy it is to mock and skip validation requests in Laravel with Mockery, and understand what makes a test a "unit" test versus just a test.

Andre Liem
Andre Liem

When you are writing tests for controllers in Laravel it’s quite common that you only want to test the functionality that occurs after validation passes and not the validation itself.

Here is a common scenario below. A UserController with a basic update function to update these fields.

class UserController
{
  public function update(UserRequest $request, User $user)
  {
      $user->update($request->all());
      // This is kept brief for a reason
  }
}

The Form Request UserRequest below validates a few fields.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string',
            'email' => [
                'required',
                'email',
                'max:255',
            ],
            'phone' => 'required|string|max:10',
        ];
    }
}

And then a test to assert the update request fails if it is passed empty data.

class UserControllerTest extends TestCase
{
  /** @test **/
  public function update_fails_without_required_fields(): void
  {
    $emptyData = [];
    $this->put(route('users.update'), $emptyData)->assertSessionHasErrors();
  }
}

If UserController was an API, we would probably want to assert the response is a 422 like this: assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)

This is nice and simple, we have a test which ensures our update function will return an error if the data is invalid.

But lets say one day you have to add functionality to this controller, where the client has asked for the following requirement:

As a user, I want the ability to set my food preferences by typing it in so that recipes shown to me are interesting to me.

User Story – User Saves Food Preference

If you’re new to “User Stories”, I recommend you read the wikipedia definition here.

Coding the solution to the above is relatively easy, we take in another parameter and store it in some table. It would be nice to write a test for this new feature as well. The only thing is I would prefer not having to write a test which also fulfills all the needs of the UserRequest validation. You can imagine that the fields to be validated could be much larger than the 3 I mentioned above. Lets say it turns into 10-15 fields in the validation request in the future. Then your test needs to create all of this data and pass it into the update function to get pass validation. The worse part is that this test is now concerned about the validation rules, so every change you make to this validation affects this test. You end up having to write code which has nothing to do with what you are trying to test, which is a big reason mocking (Mockery) exists.

What I recommend is you write one test that focuses just on form validation, and your controller test actually focus on the contents of the controller methods. So to do a “unit” test, I would consider creating a UserRequestTest as a separate piece.

This article is about the controller though, so keep on reading below to see how easy it is to mock the UserRequest so that we can just continue on.

How to mock Form Request Validation

It’s actually quite easy to mock the Form Request validation with Laravel and Mockery but I personally find that whenever I mock anything I can easily go down a rabbit hole trying to figure out how to do it. This is usually because you need to have a clear understanding of how the mocked object works, which isn’t something you always have time for.

So here is an example of how to do it so you can hopefully save some time the next time you need to skip validation.

Using the requirement from above, the controller expects the food preference to be a string value and the result is an actual id from a food preference table is stored in the users table. We need to write a test which ensures our controller is properly looking for the food preference ID (maybe creating it if it doesn’t exist), and associating this with the users table.

Below is how we can write this test, without having to create valid user data such as email, name and phone fields.

/** UserControllerTest.php **/

/** @test **/
public function update_can_store_user_food_preference(): void
{
  // Given a regular user
  $user = factory(User::class)->create();
  $savedAttributes = ['food_preference' => 'vegan'];

  // Ignore validation -> make it pass true
  $this->mock(UserRequest::class, function ($mock) {
     $mock->shouldReceive('passes')->andReturn(true);
  });
  
  // Assert that the preference is stored for this user with the correct ID
  $foodPreferenceId = FoodPreference::query()->where('label', 'vegan')->get()->first()->id;
  $this->actingAs($user)->put(route('users.update'), $savedAttributes);
  $this->assertDatabaseHas('users', [ 'id' => $user->id, 'food_preference_id' => $foodPreferenceId]);
}

It’s quite easy actually, using the Laravel mock helper we say that the passes method returns true. If we had not done this, the request would have failed because it would be missing the standard required fields.

So next time you write some logic for your controller, you may want to consider mocking the validation request so that you can focus on testing what you want to test.