DEV Community

Matt Daneshvar
Matt Daneshvar

Posted on • Edited on

Testing validation in Laravel

Laravel makes writing HTTP Tests a breeze, but writing tests for request validation can get tricky.

For example, given a controller that validates and stores a Cat, how many tests do you write?

class CatsController extends Controller
{
    public function store()
    {
        request()->validate([
            'name' => 'required',
            'lives' => 'required|integer'
        ]);

        Cat::create(request(['name', 'lives']));

        return back()->with('success', 'Cat created.');
    }
}
Enter fullscreen mode Exit fullscreen mode

The happy path

I think the easiest way to start is to write a test for the happy path:

/** @test */
public function a_user_can_create_a_cat()
{
    $this->post('/cats', ['name' => 'Jafar', 'lives' => 9])
        ->assertStatus(302);

    $this->assertDatabaseHas('cats', ['name' => 'Jafar', 'lives' => 9]);
}
Enter fullscreen mode Exit fullscreen mode

Here we ensure that if our route is hit with valid input, user gets successfully redirected and a Cat gets stored in the database.

But how do you cover the validation failure scenarios?

Testing a validation failure

My favorite approach to writing tests is to simulate an actual request a person would send from their browser as closely as I can:

/** @test */
public function a_user_cant_create_a_cat_with_invalid_lives()
{
    $this->post('/cats', ['name' => 'Jafar', 'lives' => 'One'])
        ->assertSessionHasErrors('lives')
        ->assertStatus(302);

    $this->assertDatabaseMissing('cats', ['name' => 'Jafar', 'lives' => 'One']);
}
Enter fullscreen mode Exit fullscreen mode

Here we test: given a user submits a string for the lives field, they should be redirected back with an error for the lives field and no new record should be created in the cats table.

This only covers one rule. How do we make sure all rules are covered?

Testing all failure cases

With PHPUnit, you can manage this using a data provider. Once again, my goal is to simulate real user requests as closely as I can. To do this, I focus on testing different scenarios instead of different rules:

/**
 * @test
 * @dataProvider invalidCats
 */
public function a_user_cant_store_an_invalid_cat($invalidData, $invaludFields)
{
    $this->post('/cats', $invalidData)
        ->assertSessionHasErrors($invaludFields)
        ->assertStatus(302);

    $this->assertDatabaseCount('cats', 0);
}

public function invalidCats()
{
    return [
        [
            ['name' => 'Jafar', 'lives' => 'One'],
            ['lives']
        ],
        [
            ['name' => 'Jafar'],
            ['lives']
        ],
        [
            ['lives' => 5],
            ['name']
        ]
    ];
}
Enter fullscreen mode Exit fullscreen mode

Each combination provided by invalidCats contains two elements:

  1. Values we want to test against our validation
  2. Fields we expect to be caught as invalid

This way, every invalidCats combination is tested and, each time, we assert that validation fails exactly for the fields we expect.

The assertDatabaseCount call is an extra assertion to ensure nothing was added to the database. Knowing that this test will be running for
every invalid combination, you may consider removing that to speed up your tests.

Further reading

This is just one way of testing validation with Laravel. If you are keen to see other approaches, you should check out Jason McCreary's and Daan's posts too.

How do you test your validation?

Top comments (0)