24
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.');
}
}
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]);
}
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?
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']);
}
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?
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']
]
];
}
Each combination provided by invalidCats
contains two elements:
- Values we want to test against our validation
- 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.
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?
24