Testing Length Validation in Laravel
Published on by Paul Redmond
I thought it might help people new to the Laravel framework and testing, to walk through how to test length validation. When I say length validation, I mean the constraints of length that you might want to put on a string field.
For example, let’s say that we wanted to limit the length of a user’s real name to 50 characters; or if we restrict the email address to the database column length of 255. Along with the database constraints, we should add validation constraints to the controller when creating and updating records.
When testing lengths, there are a couple of techniques I think can be helpful that I use with HTTP tests to verify that my validation is working as expected. Let’s walk through some hands-on examples of the validation portion of a controller request; we will also need a test database to demonstrate a test that interacts with a database.
Getting Started
Laravel comes with a User
model and a factory to go along with the model, so that’s what we’ll use to demonstrate our testing techniques.
First, let’s create a sample Laravel project:
laravel new length-validationcd length-validation
Next, let’s create a controller and a test case to demonstrate how I like to test length validation:
php artisan make:controller UsersControllerphp artisan make:test UserTest
The last part of our setup is defines the POST
route for creating new users in routes/web.php
:
Route::post('/users', 'UsersController@store');
I will leave it up to you to set up a testing database. If you want to use an SQLite in-memory database, update your phpunit.xml
file to the following:
<php> <env name="APP_ENV" value="testing"/> <env name="DB_CONNECTION" value="sqlite"/> <env name="DB_DATABASE" value=":memory:"/> <env name="CACHE_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/> <env name="QUEUE_DRIVER" value="sync"/></php>
Setting Up Validation
Our first task is setting up validation in the controller. When validation succeeds we are just going to return a simple JSON response, however, you might be redirecting the user or doing something else after creating the record. We’ll keep it simple though, so we can focus on how to test length validation:
<?php namespace App\Http\Controllers; use App\User;use Illuminate\Http\Request;use Illuminate\Support\Facades\Hash; class UsersController extends Controller{ public function store(Request $request) { $data = $request->validate([ 'name' => 'required|max:50', 'email' => 'required|email|max:255', 'password' => 'required', ]); return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); }}
We’re using the max
validation rule to impose a maximum length value on our fields, which we are ready to test!
Testing Max Length Validation
First, let’s look at how to test to ensure that validation fails when our fields are over the max
value. We are not double-checking that the Laravel max
validation rule works properly here, our purpose is defining business requirements for length, that when changed, should trigger failed tests. If we remove the max
requirement in the future, the tests act as a safety-harness to remind us of the business rules associated with the data.
Let’s open up the (potentially poorly named) tests/Feature/UserTest.php
file and write our first test:
<?php namespace Tests\Feature; use App\User;use Tests\TestCase;use Illuminate\Foundation\Testing\RefreshDatabase; class UserTest extends TestCase{ use RefreshDatabase; public function setUp() { parent::setUp(); $this->user = factory(User::class)->make(); } /** @test */ function name_should_not_be_too_long() { $response = $this->post('/users', [ 'name' => str_repeat('a', 51), 'email' => $this->user->email, 'password' => 'secret', ]); $response->assertStatus(302); $response->assertSessionHasErrors([ 'name' => 'The name may not be greater than 50 characters.' ]); }}
Our first test makes a request to the /users
endpoint with a name that is just outside the boundary of our max rule using PHP’s str_repeat()
function. We rely on the email address field from a new User
instance we are creating before each test runs.
By default, validation will redirect the user when validation fails, so we check for a 302
status code and verify that our validation error is in the session.
Testing the Bounds of Validation
Although not always critical, I like to test the inner bounds of length validation, ensuring that a field that is just the right amount of length passes:
/** @test */function name_is_just_long_enough_to_pass(){ $response = $this->post('/users', [ 'name' => str_repeat('a', 50), 'email' => $this->user->email, 'password' => 'secret', ]); $this->assertDatabaseHas('users', [ 'email' => $this->user->email, ]); $response->assertStatus(201);}
This test ensures that a name
that is just the right length and that the request will still end up storing the user in the database.
Testing the Email Address
The email address length validation tests are almost identical, but I wanted to show you the slight difference:
/** @test */function email_should_not_be_too_long(){ $response = $this->post('/users', [ 'name' => $this->user->name, 'email' => str_repeat('a', 247).'@test.com', // 256 ]); $response->assertStatus(302); $response->assertSessionHasErrors([ 'email' => 'The email may not be greater than 255 characters.' ]);}
In this case, we take the difference of @test.com
and repeat a valid email character that makes a total string length of 256
characters.
Here’s an example of the email string sent:
php artisan tinkerstr_repeat('a', 247).'@test.com'=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@test.com">>> strlen(str_repeat('a', 247).'@test.com')=> 256
Testing Length Validation Rules in Separate Tests
You might be asking: “why don’t we just test all of the length validation rules in one test?” You could definitely do that, but I feel that the separation allows you to focus isolating the single validation rule on an otherwise valid request.
The test reads a little clearer too, so I think the extra testing verbosity reads more clearly to developers that might be reading the tests. For example, the test “name_should_not_be_too_long” is much clearer that something like “test_validation_rules.”
“Between” Validation Tests
When you’re testing validation between two values, you want to test the lower and upper bounds of the validation rule. For example, let’s say we wanted our name
field to be between 10
characters and 50
:
$data = $request->validate([ 'name' => 'required|between:10,50', 'email' => 'required|email|max:255', 'password' => 'required',]);
In this case, you might want to test the following conditions:
- when a name is just too short
- when the name is just the minimum amount required
- when the name is just the maximum amount required
- when the name is too long
You might feel confident only testing too short or too long scenarios, but I think it’s worth showing you how to check scenarios when the field is valid but on the edge of the constraints. You will get a feel for when you need to test every situation, and usually just checking the “too long” or “too short” is good enough!
Testing Multiple Cases
I think it’s worth bringing up that validation testing is a good place to try out multiple cases of valid or invalid data.
Let’s say that you needed to write your own email validation rule with custom requirements, and you want to ensure that the validation rules work in a variety of cases.
You could make the decision to unit test a custom rule, but the principle is the same: loop through multiple cases and makes assertions on each.
/** @test */function email_validation_should_reject_invalid_emails(){ collect(['you@example,com', 'bad_user.org', 'example@bad+user.com'])->each(function ($invalidEmail) { $this->post('/users', [ 'name' => $this->user->name, 'email' => $invalidEmail, 'password' => 'secret', ])->assertSessionHasErrors([ 'email' => 'The email must be a valid email address.' ]); });}
Closing Thoughts
In this beginner testing tutorial, I showed you how I go about testing validation boundaries for string lengths. The string length validation might be to match up with a database column length, ensuring the data meets a business requirement, or both.
String columns typically should have some validation length that associates with the database column type (i.e. varchar(255)
), and testing the max length allowed ensures that your controllers validate strings before they get inserted into the database improperly.
I also showed you an example of how writing a test for each validation scenario might make tests clearer to read by singling out each validation rule’s requirements.