30 Days of Automated Testing:Using PHPUnit【D08】
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 Factory
may 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!