Testing Length Validation in Laravel

Published on by

Testing Length Validation in Laravel image

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-validation
cd 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 UsersController
php 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 tinker
str_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.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Bluesky notification channel for Laravel image

Bluesky notification channel for Laravel

Read article
Sprout Multitenancy Package for Laravel image

Sprout Multitenancy Package for Laravel

Read article
Creating a CLI Application With Laravel and Docker image

Creating a CLI Application With Laravel and Docker

Read article
Laravel Herd Adds Native MongoDB Support image

Laravel Herd Adds Native MongoDB Support

Read article
How to Create Tech Videos for YouTube with Josh Cirre image

How to Create Tech Videos for YouTube with Josh Cirre

Read article
Integrate Unsplash in Your Laravel Application image

Integrate Unsplash in Your Laravel Application

Read article