Laravel (5.7) Database Testing
Introduction
Laravel provides us with a variety of helpful tools to make it easier to test our database driven applications. First, we may use the assertDatabaseHas helper to assert that data exists in the database matching a given set of criteria. For instance, if we would like to verify that there is a record in the users table with the email value of [email protected], we can do the following:
public function testDatabase()
{
// Make call to application...
$this->assertDatabaseHas('users', [
'email' => '[email protected]'
]);
}
We can also use the assertDatabaseMissing helper to assert that data does not exist in the database.
The assertDatabaseHas method and other related helpers like it are for convenience. we are free to use any of PHPUnit's built-in assertion methods to supplement our tests.
Generating Factories
To generate a factory, you can use the make:factory Artisan command:
php artisan make:factory PostFactory
The new factory is placed in your database/factories directory.
The --model option can be used to indicate the name of the model created by the factory. This option pre-fills the generated factory file with the given model:
php artisan make:factory PostFactory --model=Post
Resetting The Database After Each Test
It is often useful to reset your database after each test so that the data from a previous test doesn't interfere with subsequent tests. The RefreshDatabase trait will take the most optimal approach to migrating your test database depending on if you are using an in-memory database or a traditional database. When you use the trait on your test class, everything will be handled for you:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
Writing Factories
When testing, there may be need to insert a few records into your database before executing your test. Rather than manually specifying the value of each column when you create this test data, Laravel allows define a default set of attributes for each of your Eloquent models using model factories. If you want to get started, you should take a look at the database/factories/UserFactory.php file in your application. This file contains one factory definition out of the box:
use Illuminate\Support\Str;
use Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => Str::random(10),
];
});
Within the Closure, that serves as the factory definition, you can return the default test values of all attributes on the model. The Closure receives an instance of the Faker PHP library, which will allow you to conveniently generate various kinds of random data for testing.
You can also create additional factory files for each model for better organization. For instance, you can create UserFactory.php and CommentFactory.php files within your database/factories directory. All files within the factories directory are automatically loaded by Laravel.
Factory States
States allow you to define the discrete modifications that can be applied to your model factories in any combination. For instance, your User model might have a delinquent state which modifies one of its default attribute values. You can define your state transformations using the state method. For simple states, you can pass an array of attribute modifications:
$factory->state(App\User::class, 'delinquent', [
'account_status' => 'delinquent',
]);
In the case where your state requires calculation or a $faker instance, you can use a Closure to calculate the state's attribute modifications:
$factory->state(App\User::class, 'address', function ($faker) {
return [
'address' => $faker->address,
];
});
Factory Callbacks
All Factory callbacks are registered using the afterMaking and afterCreating methods, these callbacks allow you to perform additional tasks after making or creating a model. For instance, you can use callbacks to relate additional models to the created model:
$factory->afterMaking(App\User::class, function ($user, $faker) {
// ...
});
$factory->afterCreating(App\User::class, function ($user, $faker) {
$user->accounts()->save(factory(App\Account::class)->make());
});
You can also define callbacks for factory states:
$factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
// ...
});
$factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
// ...
});
Using Factories
Creating Models
Immediately you have defined your factories, you can use the global factory function in your tests or seed files to generate model instances. So, let us consider a few examples of creating models. First, we will use the make method to create models but not save them to the database:
public function testDatabase()
{
$user = factory(App\User::class)->make();
// Use model in tests...
}
You could also create a Collection of many models or create models of a given type:
// Creates three App\User instances...
$users = factory(App\User::class, 3)->make();
Applying States
You can also apply any of your states to the models. If you want to apply multiple state transformations to the models, you need to specify the name of each state you would like to apply:
$users = factory(App\User::class, 5)->states('delinquent')->make();
$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();
Overriding Attributes
If you want to override some of the default values of your models, you can pass an array of values to the make method. Only the specified values are replaced while the rest of the values remain set to their default values as specified by the factory:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
Persisting Models
The create method does not only create the model instances but it also saves them to the database using Eloquent's save method:
public function testDatabase()
{
// Create a single App\User instance...
$user = factory(App\User::class)->create();
// Create three App\User instances...
$users = factory(App\User::class, 3)->create();
// Use model in tests...
}```
You can override attributes that are on the model by passing an array to the create method:
```
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
Relationships
In example below, we will attach a relation to some created models. When you are using the create method to create multiple models, an Eloquent collection instance will be returned, this allows you to use any of the convenient functions provided by the collection, such as each:
$users = factory(App\User::class, 3)
->create()
->each(function ($user) {
$user->posts()->save(factory(App\Post::class)->make());
});
Relations & Attribute Closures
You can also attach relationships to models using Closure attributes in your factory definitions. For instance, if you want to create a new User instance when creating a Post, you can do the following:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
}
];
});
These Closures will also receive the evaluated attribute array of the factory that defines them:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'user_type' => function (array $post) {
return App\User::find($post['user_id'])->type;
}
];
});
Available Assertions
Laravel provides you several database assertions for your PHPUnit tests:
Method | Description |
---|---|
$this->assertDatabaseHas($table, array $data); | Asserts that a table in the database contains the given data. |
$this->assertDatabaseMissing($table, array $data); | Asserts that a table in the database does not contain the given data. |
$this->assertSoftDeleted($table, array $data); | Asserts that the given record has been soft deleted. |
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://www.w3resource.com/laravel/database-testing.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics