Testing validation in Laravel
Tags: laravel, php, testing, validation
Published: 2021-07-09 03:02:56

Laravel makes writing [HTTP Tests](https://laravel.com/docs/8.x/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?
```php
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.');
    }
}
```

## The happy path
I think the easiest way to start is to write a test for the happy path:
```php
/** @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]);
}
```

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:

```php
/** @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']);
}
```
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](https://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers). 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*:
```php
/**
 * @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']
        ]
    ];
}
```
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](https://jasonmccreary.me/articles/test-validation-laravel-form-request-assertion/) and [Daan's](https://medium.com/@daaaan/a-guide-to-unit-testing-laravel-form-requests-in-a-different-way-f1bdb6d86053) posts too.

How do you test your validation?