Testing Length Validation in Laravel


February 20th, 2018


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:

1laravel new length-validation
2cd length-validation

Next, let’s create a controller and a test case to demonstrate how I like to test length validation:

1php artisan make:controller UsersController
2php artisan make:test UserTest

The last part of our setup is defines the POST route for creating new users in routes/web.php:

1Route::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:

2 <env name="APP_ENV" value="testing"/>
3 <env name="DB_CONNECTION" value="sqlite"/>
4 <env name="DB_DATABASE" value=":memory:"/>
5 <env name="CACHE_DRIVER" value="array"/>
6 <env name="SESSION_DRIVER" value="array"/>
7 <env name="QUEUE_DRIVER" value="sync"/>

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:

3namespace App\Http\Controllers;
5use App\User;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Hash;
9class UsersController extends Controller
11 public function store(Request $request)
12 {
13 $data = $request->validate([
14 'name' => 'required|max:50',
15 'email' => 'required|email|max:255',
16 'password' => 'required',
17 ]);
20 return User::create([
21 'name' => $data['name'],
22 'email' => $data['email'],
23 'password' => Hash::make($data['password']),
24 ]);
25 }

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:

3namespace Tests\Feature;
5use App\User;
6use Tests\TestCase;
7use Illuminate\Foundation\Testing\RefreshDatabase;
9class UserTest extends TestCase
11 use RefreshDatabase;
13 public function setUp()
14 {
15 parent::setUp();
17 $this->user = factory(User::class)->make();
18 }
20 /** @test */
21 function name_should_not_be_too_long()
22 {
23 $response = $this->post('/users', [
24 'name' => str_repeat('a', 51),
25 'email' => $this->user->email,
26 'password' => 'secret',
27 ]);
29 $response->assertStatus(302);
30 $response->assertSessionHasErrors([
31 'name' => 'The name may not be greater than 50 characters.'
32 ]);
33 }

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:

1/** @test */
2function name_is_just_long_enough_to_pass()
4 $response = $this->post('/users', [
5 'name' => str_repeat('a', 50),
6 'email' => $this->user->email,
7 'password' => 'secret',
8 ]);
10 $this->assertDatabaseHas('users', [
11 'email' => $this->user->email,
12 ]);
13 $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:

1/** @test */
2function email_should_not_be_too_long()
4 $response = $this->post('/users', [
5 'name' => $this->user->name,
6 'email' => str_repeat('a', 247).'@test.com', // 256
7 ]);
9 $response->assertStatus(302);
10 $response->assertSessionHasErrors([
11 'email' => 'The email may not be greater than 255 characters.'
12 ]);

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:

1php artisan tinker
2str_repeat('a', 247).'@test.com'
3=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@test.com"
4>>> strlen(str_repeat('a', 247).'@test.com')
5=> 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:

1$data = $request->validate([
2 'name' => 'required|between:10,50',
3 'email' => 'required|email|max:255',
4 '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.

1/** @test */
2function email_validation_should_reject_invalid_emails()
4 collect(['you@example,com', 'bad_user.org', 'example@bad+user.com'])->each(function ($invalidEmail) {
5 $this->post('/users', [
6 'name' => $this->user->name,
7 'email' => $invalidEmail,
8 'password' => 'secret',
9 ])->assertSessionHasErrors([
10 'email' => 'The email must be a valid email address.'
11 ]);
12 });

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.

Filed in:

Paul Redmond

Full stack web developer. Author of Lumen Programming Guide and Docker for PHP Developers.