30 Days of Automated Testing:Using PHPUnit【D08】

Database Testing

WilliamP
4 min readJan 22, 2023

In the previous article, we practiced the basic testing methods for APIs. Today, let’s take a look at the testing methods for databases!

Preparation: Factory & UserRepository

Before starting to implement database testing, let me introduce Factory to you.

Factory is a feature provided by Laravel's ORM: Eloquent, which allows us to prepare test data in a simple way. After initializing Laravel, a UserFactory is prepared by default. (The reason why Laravel officially prepared this Factorymay be because the most basic default table is users) Conventionally, Factory files are placed in the database/factories directory and are named XxxFactory, where Xxx is the resource name.

  • database/factories/UserFactory.php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}

/**
* Indicate that the model's email address should be unverified.
*
* @return static
*/
public function unverified()
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

The first main way of using Factory is that it will create data in the actual table and respond with Eloquent Entity, the usage is as follows:

$user = User::facotry()->create();

The above code will create a User data in the database immediately. The fields name and email will use the fake()function to randomly generate fake values, email_verified_at will use the current time, and remember_token will be a randomly generated string of 10 characters.

The other way is that it won’t create data in the database, but will still return an Eloquent Entity, and the usage is as follows:

$user = User::facotry()->make();

But keep in mind that when using make() to create a data entity, its id field value will be empty (because it is not actually creating the data in the database).

In addition, you can also specify the field values you want when creating data:

$user = User::facotry()->create(['name' => 'william']);

The above is an introduction to Factory, and of course, there are more interesting technical details worth discussing, such as the mysterious fake(). However, let's explore this in future articles!

Main Part

After understanding the way to prepare data, let’s take a look at the object we’re going to test today. Below is the UserRepository class, which is implemented for the User resource:

  • app/Repositories/UserRepository.php
<?php

namespace App\Repositories;

use App\Models\User;

class UserRepository
{
protected $model;

public function __construct(User $model)
{
$this->model = $model;
}

// To create User record by given data
public function createUser(array $data)
{
return $this->model::create($data);
}

// Update name of User data with given ID
public function updateUserNameById($userId, string $name)
{
$user = $this->getUserById($userId);

if (empty($user)) {
return false;
}

$user->name = $name;

return $user->save();
}

// Delete the User data with given ID
public function deleteUserById($userId)
{
$user = $this->getUserById($userId);

if (empty($user)) {
return false;
}

return $user->delete();
}

// Retrieve User data with given ID
public function getUserById($userId)
{
return $this->model::find($userId);
}
}

The code above implements CRUD operations for the User resource, next we will write tests!

Data Operation Testing:Creation

First, let’s test the creatinng data feature! Please take a look at the following code:

namespace Tests\Feature;

use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserRepositoryTest extends TestCase
{
use RefreshDatabase;

/**
* Example for user creating
* @return void
*/
public function testUserCreating()
{
$repository = app(UserRepository::class);

$repository->createUser([
'name' => 'test_name',
'email' => 'test@test.test',
'password' => 'password',
]);

$this->assertDatabaseHas('users', [
'name' => 'test_name',
'email' => 'test@test.test',
'password' => 'password',
]);
}
}

The above code calls the createUser() method, and after the call, it verifies that the expected data is present in the database.

Data Operation Testing:Retrieve

namespace Tests\Feature;

use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserRepositoryTest extends TestCase
{
use RefreshDatabase;

/**
* Example for user reading
* @return void
*/
public function testUserReading()
{
$user = User::factory()->create();
$repository = app(UserRepository::class);

$userGot = $repository->getUserById($user->id);

$this->assertEquals($user->id, $userGot->id);
}
}

The above code will first create a fake data $user, then call getUserById() and after the call, it will verify that the ID of the data obtained is as expected.

Data Operation Testing:Updating

namespace Tests\Feature;

use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserRepositoryTest extends TestCase
{
use RefreshDatabase;

/**
* Example for user updating
* @return void
*/
public function testUserUpdating()
{
$user = User::factory()->create([
'name' => 'test_name',
'email' => 'test@test.test',
'password' => 'password',
]);
$repository = app(UserRepository::class);

$repository->updateUserNameById($user->id, 'name_2');

$this->assertDatabaseHas('users', [
'name' => 'name_2',
]);
}
}

The above code will first create a fake user $user, then call updateUserNameById() and after the call, it verifies if the data has been updated as expected.

Data Operation Testing:Deleting

namespace Tests\Feature;

use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserRepositoryTest extends TestCase
{
use RefreshDatabase;

/**
* Example for user deletion
* @return void
*/
public function testUserDeletion()
{
$user = User::factory()->create([
'name' => 'test_name',
'email' => 'test@test.test',
'password' => 'password',
]);
$userId = $user->id;
$repository = app(UserRepository::class);

$repository->deleteUserById($user->id);

$this->assertDatabaseMissing('users', [
'id' => $userId,
]);
}
}

The above code will first create a fake data $user, and then call deleteUseryId(). After the call, it will verify that the data is deleted as expected.

The above is today’s introduction to database testing.

In the next article, we will introduce you to Traits related to automated testing.

If you liked this article or found it helpful, feel free to give it some claps and follow the author!

Reference

Articles of This Series

--

--