A simple way to test your APIs with Laravel

Andrea Pollastri
3 min readJun 8, 2023

Software Testing is a complex and deep argument, so this short story is for educational purposes because I think is very important, for our project maintenance and scalability, to test every aspect of our Software Business Logic.

I will show you a “stupid” but simple example of a Feature Test based on a fresh installation of Laravel 10.

The first step is configuring our phpunit.xml file and setting these parameters:

<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>

Our “fake” business logic is based on the User model (default Laravel model) and, for convenience, is written directly into “web” route file:

// routes/web.php

Route::post('/user', function () {

// Just for testing scopes
request()->validate([
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => [
'required',
Password::min(8)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised()
]
]);

$user = new User();
$user->name = request()->name;
$user->email = request()->email;
$user->password = request()->password;
$user->save();

// "Dummy" logic
$savedUser = User::where('email', request()->email)->first();
return response()->json($savedUser);
});

Now we are ready to create a Test class in our Test Suite:

php artisan make:test UserTest 

In this new class, we will create some test cases about our logic, e.g.:

// tests/Feature/UserTest.php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\User;
use Illuminate\Support\Facades\Session;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class UserTest extends TestCase
{
use DatabaseMigrations;
use RefreshDatabase;

protected $name = 'John Doe';
protected $email = 'john.doe@gmail.com';
protected $password = '!C0mp1ex#S3cr3T-Pa55_';
protected $wrongEmailFormat = 'Simple String';
protected $weakPassword = '123456';
protected $compromisedPassword = 'password';

public function test_is_it_possible_to_create_users(): void
{
Session::start();

$response = $this->post('/user', [
'name' => $this->name,
'email' => $this->email,
'password' => $this->password,
'_token' => csrf_token()
], [
'Accept' => 'application/json'
]);

$response->assertJsonFragment([
'id' => 1,
'name' => $this->name,
'email' => $this->email,
'email_verified_at' => NULL
]);

$response->assertStatus(200);

}


public function test_user_email_has_to_be_unique(): void
{
Session::start();

$user = new User();
$user->name = $this->name;
$user->email = $this->email;
$user->password = 'Secret';
$user->save();

$response = $this->post('/user', [
'name' => $this->name,
'email' => $this->email,
'password' => $this->password,
'_token' => csrf_token()
], [
'Accept' => 'application/json'
]);

$response->assertStatus(422);

}

public function test_user_email_has_to_be_real(): void
{
Session::start();

$response = $this->post('/user', [
'name' => $this->name,
'email' => $this->wrongEmailFormat,
'password' => $this->password,
'_token' => csrf_token()
], [
'Accept' => 'application/json'
]);

$response->assertStatus(422);

}

public function test_user_password_has_to_be_complex(): void
{
Session::start();

$response = $this->post('/user', [
'name' => $this->name,
'email' => $this->email,
'password' => $this->weakPassword,
'_token' => csrf_token()
], [
'Accept' => 'application/json'
]);

$response->assertStatus(422);

}

public function test_user_password_has_to_be_uncompromised(): void
{
Session::start();

$response = $this->post('/user', [
'name' => $this->name,
'email' => $this->email,
'password' => $this->compromisedPassword,
'_token' => csrf_token()
], [
'Accept' => 'application/json'
]);

$response->assertStatus(422);

}

}

To run tests:

php artisan test

Other test snippets…

private function createAdminUser()
{
Role::create(['name' => 'admin']);
$user = User::factory()->create();
$user->assignRole('admin');

return $user;
}

private function mockRequest($user, $endpoint, $method = 'GET', array $payload = [])
{
return $this->withHeaders([
'Accepted' => 'application/json',
'Authorization' => ($user) ? 'Bearer '.$user->createToken('test-token')->plainTextToken : '',
])->json($method, $endpoint, $payload);
}

public function test_count_index(): void
{
Customer::factory()->count(3)->person()->create();

$response = $this->mockRequest($this->createAdminUser(), '/api/customers');

$response->assertJsonCount(3);
}

public function test_correct_customers_store(): void
{
$this->seed();

$response = $this->mockRequest(
$this->createAdminUser(),
'/api/customers',
'POST',
[
'firstname' => 'John',
'lastname' => 'Doe',
'phone' => '+012 123456789',
'email' => 'john@doe.ltd',
]
);

$response->assertStatus(200);

$this->assertDatabaseHas('customers', [
'id' => 1,
'firstname' => 'John',
'lastname' => 'Doe',
'phone' => '+012 123456789',
'email' => 'john@doe.ltd',
]);
}

For more info: https://laravel.com/docs/10.x/testing

Hope you will cover all your code with amazing tests :)

--

--

Andrea Pollastri

I'm a Web Artisan, Open Source supporter, and OWASP member based in Milan, Italy.