Laravel 9 — correct email validation 🔐

Lukasz Lupa
5 min readFeb 11, 2023
Photo by Mohammad Rahmani on Unsplash

I had an error during an automated test, some time ago. It was quite an interesting bug, related to the e-mail address validation, so today I will show you, how good use Faker and the freeEmail function with tests.

Begin

Oh! I hope Laravel is familiar to you ❤️

Our application needs an endpoint where users can create their accounts. They will need an e-mail address, password and nickname. Let’s go!

First, we will run a few commands:

php artisan make:controller AuthController
php artisan make:request UserRegisterRequest

Good 👍 Now we will go to a controller of our endpoint.

<?php

namespace App\Http\Controllers;

use App\Http\Requests\UserRegisterRequest;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;

class UserRegisterController extends Controller
{
public function register(UserRegisterRequest $request): JsonResponse
{
return new JsonResponse(
data: User::query()->create($request->validated()),
status: Response::HTTP_CREATED,
);
}
}

Next – Form Request 👾

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRegisterRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => 'required|email|max:255',
'password' => 'required',
'name' => 'required|max:200'
];
}
}

And in the end — routing 💻

Route::group(['prefix' => 'auth', 'as' => 'auth.'], function () {
Route::post('/register', [AuthController::class, 'register'])->name('register');
});

Perfect! 🎉

As you can see, we used the email rule to check the e-mail address. This method is suitable, but has a disadvantage — it does not check whether the given address exists.

Let’s write a small test.

<?php

namespace Tests\Feature;

use Tests\APITestCase;

class AuthControllerTest extends APITestCase
{
/** @test */
public function user_can_create_an_account()
{
// Send a request.
$response = $this->postJson(
route('auth.register'),
[
'email' => $this->faker->unique()->email,
'password' => 'Th1s1sVeryGoodP@ssword',
'name' => $this->faker->name,
]
);

// Check if the request was successful
$response->assertSuccessful();
}
}

Result:

Okay 👍 Now let’s check what’s wrong with the email rule. Take the same test file and change one parameter:

<?php

namespace Tests\Feature;

use Tests\APITestCase;

class AuthControllerTest extends APITestCase
{
/** @test */
public function user_can_create_an_account()
{
// Send a request.
$response = $this->postJson(
route('auth.register'),
[
'email' => 'lukasz@hello', // <--- Here my friend!
'password' => 'Th1s1sVeryGoodP@ssword',
'name' => $this->faker->name,
]
);

// Check if the request was successful
$response->assertSuccessful();
}
}

What’s the result?

Pass? This is an imperfection email rule with the RFC parameter as default. How we can fix this?

Solution

We need to add an extra parameter to the email rule in our form request file. Let’s add it!

Go to UserRegisterRequest and update it.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRegisterRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns|max:255',
'password' => 'required',
'name' => 'required|max:200'
];
}
}

Great. Now we can try to run the test again.

Super! This is what we need. Now, nobody can use strange emails like: ‘lukasz@hello’, ‘john@doe’, etc.

We can now safely go back to our test file and revert changes. We back the use of Faker to generate the email address.

<?php

namespace Tests\Feature;

use Tests\APITestCase;

class AuthControllerTest extends APITestCase
{
/** @test */
public function user_can_create_an_account()
{
// Send a request.
$response = $this->postJson(
route('auth.register'),
[
'email' => $this->faker->unique()->email,
'password' => 'Th1s1sVeryGoodP@ssword',
'name' => $this->faker->name,
]
);

// Check if the request was successful
$response->assertSuccessful();
}
}

At this point, you could finish your task, push the changes to git, create a Pull Request and the matter is done.

Are you sure? I don’t think so 😔

I can bet that from time to time you would receive information that the test failed. Restarting it would help, but for large/huge applications that use the ‘email’ validation rule in multiple places, it can be very problematic.

But what is the problem? 😖

Faker->email

The ‘email’ function generates a random email address with a random domain, so there’s a chance that it will be nonexistent — if you know what I mean.
There are examples in the documentation: ‘orval.treutel@blick.com’, ‘hickle.lavern@erdman.com

So is there any other alternative?

Faker->freeEmail

Looking through the Faker documentation, I found a great solution: freeEmail. This function generates an email address using a list of allowed domains, so we can assume that this domain will be existing and our rule will be able to check the DNS.

In the Provider folder of the FakerPHP package, you can find folders sorted by all regions. For the UK (I live and work here) the folder is en_GB. In the folder we will find the files:

We need open the Internet.php file and there is a static property called $freeEmailDomain.

protected static $freeEmailDomain = ['gmail.com', 'yahoo.com', 'hotmail.com', 'gmail.co.uk', 'yahoo.co.uk', 'hotmail.co.uk'];

You can check another file here:

Big final 🎉

With a clear conscience, we can go back to our test and make changes:

<?php

namespace Tests\Feature;

use Tests\APITestCase;

class AuthControllerTest extends APITestCase
{
/** @test */
public function user_can_create_an_account()
{
// Send a request.
$response = $this->postJson(
route('auth.register'),
[
'email' => $this->faker->unique()->freeEmail,
'password' => 'Th1s1sVeryGoodP@ssword',
'name' => $this->faker->name,
]
);

// Check if the request was successful
$response->assertSuccessful();
}
}
Photo by Ian Stauffer on Unsplash

I hope I helped at least one person who had the same problem as me.

👉 Please clap if you liked my article.
👉 Comment if you were that person (with the problem).

--

--

Lukasz Lupa

Full Stack Developer with 5 years experience - Father, & Freelancer - Laravel & Nest JS lover