Introduction
Laravel provides a variety of helpful tools and assertions to make it easier to test your database driven applications. In addition, Laravel model factories and seeders make it painless to create test database records using your application's Eloquent models and relationships. We'll discuss all of these powerful features in the following documentation.
Resetting The Database After Each Test
Before proceeding much further, let's discuss how to reset your
database after each of your tests so that data from a previous test does
not interfere with subsequent tests. Laravel's included
Illuminate\Foundation\Testing\RefreshDatabase
trait will
take care of this for you. Simply use the trait on your test class:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* A basic functional test example.
*
* @return void
*/
public function test_basic_example()
{
$response = $this->get('/');
// ...
}
}
Defining Model Factories
Concept Overview
First, let's talk about Eloquent model factories. When testing, you may need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a set of default attributes for each of your Eloquent models using model factories.
To see an example of how to write a factory, take a look at the
database/factories/UserFactory.php
file in your
application. This factory is included with all new Laravel applications
and contains the following factory definition:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}
As you can see, in their most basic form, factories are classes that
extend Laravel's base factory class and define definition
method. The definition
method returns the default set of
attribute values that should be applied when creating a model using the
factory.
Via the faker
property, factories have access to the Faker PHP library, which
allows you to conveniently generate various kinds of random data for
testing.
Tip!! You can set your application's Faker locale by adding a
faker_locale
option to yourconfig/app.php
configuration file.
Generating Factories
To create a factory, execute the make:factory
Artisan command:
php artisan make:factory PostFactory
The new factory class will be placed in your
database/factories
directory.
Model & Factory Discovery Conventions
Once you have defined your factories, you may use the static
factory
method provided to your models by the
Illuminate\Database\Eloquent\Factories\HasFactory
trait in
order to instantiate a factory instance for that model.
The HasFactory
trait's factory
method will
use conventions to determine the proper factory for the model the trait
is assigned to. Specifically, the method will look for a factory in the
Database\Factories
namespace that has a class name matching
the model name and is suffixed with Factory
. If these
conventions do not apply to your particular application or factory, you
may overwrite the newFactory
method on your model to return
an instance of the model's corresponding factory directly:
use Database\Factories\Administration\FlightFactory;
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return FlightFactory::new();
}
Next, define a model
property on the corresponding
factory:
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Flight::class;
}
Factory States
State manipulation methods allow you to define discrete modifications
that can be applied to your model factories in any combination. For
example, your Database\Factories\UserFactory
factory might
contain a suspended
state method that modifies one of its
default attribute values.
State transformation methods typically call the state
method provided by Laravel's base factory class. The state
method accepts a closure which will receive the array of raw attributes
defined for the factory and should return an array of attributes to
modify:
/**
* Indicate that the user is suspended.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function suspended()
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}
Factory Callbacks
Factory callbacks are registered using the afterMaking
and afterCreating
methods and allow you to perform
additional tasks after making or creating a model. You should register
these callbacks by defining a configure
method on your
factory class. This method will be automatically called by Laravel when
the factory is instantiated:
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* Configure the model factory.
*
* @return $this
*/
public function configure()
{
return $this->afterMaking(function (User $user) {
//
})->afterCreating(function (User $user) {
//
});
}
// ...
}
Creating Models Using Factories
Instantiating Models
Once you have defined your factories, you may use the static
factory
method provided to your models by the
Illuminate\Database\Eloquent\Factories\HasFactory
trait in
order to instantiate a factory instance for that model. Let's take a
look at a few examples of creating models. First, we'll use the
make
method to create models without persisting them to the
database:
use App\Models\User;
public function test_models_can_be_instantiated()
{
$user = User::factory()->make();
// Use model in tests...
}
You may create a collection of many models using the
count
method:
$users = User::factory()->count(3)->make();
Applying States
You may also apply any of your states to the models. If you would like to apply multiple state transformations to the models, you may simply call the state transformation methods directly:
$users = User::factory()->count(5)->suspended()->make();
Overriding Attributes
If you would like to override some of the default values of your
models, you may pass an array of values to the make
method.
Only the specified attributes will be replaced while the rest of the
attributes remain set to their default values as specified by the
factory:
$user = User::factory()->make([
'name' => 'Abigail Otwell',
]);
Alternatively, the state
method may be called directly
on the factory instance to perform an inline state transformation:
$user = User::factory()->state([
'name' => 'Abigail Otwell',
])->make();
Tip!! Mass assignment protection is automatically disabled when creating models using factories.
Persisting Models
The create
method instantiates model instances and
persists them to the database using Eloquent's save
method:
use App\Models\User;
public function test_models_can_be_persisted()
{
// Create a single App\Models\User instance...
$user = User::factory()->create();
// Create three App\Models\User instances...
$users = User::factory()->count(3)->create();
// Use model in tests...
}
You may override the factory's default model attributes by passing an
array of attributes to the create
method:
$user = User::factory()->create([
'name' => 'Abigail',
]);
Sequences
Sometimes you may wish to alternate the value of a given model
attribute for each created model. You may accomplish this by defining a
state transformation as a sequence. For example, you may wish to
alternate the value of an admin
column between
Y
and N
for each created user:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory()
->count(10)
->state(new Sequence(
['admin' => 'Y'],
['admin' => 'N'],
))
->create();
In this example, five users will be created with an
admin
value of Y
and five users will be
created with an admin
value of N
.
If necessary, you may include a closure as a sequence value. The closure will be invoked each time the sequence needs a new value:
$users = User::factory()
->count(10)
->state(new Sequence(
fn ($sequence) => ['role' => UserRoles::all()->random()],
))
->create();
Within a sequence closure, you may access the $index
or
$count
properties on the sequence instance that is injected
into the closure. The $index
property contains the number
of iterations through the sequence that have occurred thus far, while
the $count
property contains the total number of times the
sequence will be invoked:
$users = User::factory()
->count(10)
->sequence(fn ($sequence) => ['name' => 'Name '.$sequence->index])
->create();
Factory Relationships
Has Many Relationships
Next, let's explore building Eloquent model relationships using
Laravel's fluent factory methods. First, let's assume our application
has an App\Models\User
model and an
App\Models\Post
model. Also, let's assume that the
User
model defines a hasMany
relationship with
Post
. We can create a user that has three posts using the
has
method provided by the Laravel's factories. The
has
method accepts a factory instance:
use App\Models\Post;
use App\Models\User;
$user = User::factory()
->has(Post::factory()->count(3))
->create();
By convention, when passing a Post
model to the
has
method, Laravel will assume that the User
model must have a posts
method that defines the
relationship. If necessary, you may explicitly specify the name of the
relationship that you would like to manipulate:
$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();
Of course, you may perform state manipulations on the related models. In addition, you may pass a closure based state transformation if your state change requires access to the parent model:
$user = User::factory()
->has(
Post::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
)
->create();
Using Magic Methods
For convenience, you may use Laravel's magic factory relationship
methods to build relationships. For example, the following example will
use convention to determine that the related models should be created
via a posts
relationship method on the User
model:
$user = User::factory()
->hasPosts(3)
->create();
When using magic methods to create factory relationships, you may pass an array of attributes to override on the related models:
$user = User::factory()
->hasPosts(3, [
'published' => false,
])
->create();
You may provide a closure based state transformation if your state change requires access to the parent model:
$user = User::factory()
->hasPosts(3, function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
->create();
Belongs To Relationships
Now that we have explored how to build "has many" relationships using
factories, let's explore the inverse of the relationship. The
for
method may be used to define the parent model that
factory created models belong to. For example, we can create three
App\Models\Post
model instances that belong to a single
user:
use App\Models\Post;
use App\Models\User;
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();
If you already have a parent model instance that should be associated
with the models you are creating, you may pass the model instance to the
for
method:
$user = User::factory()->create();
$posts = Post::factory()
->count(3)
->for($user)
->create();
Using Magic Methods
For convenience, you may use Laravel's magic factory relationship
methods to define "belongs to" relationships. For example, the following
example will use convention to determine that the three posts should
belong to the user
relationship on the Post
model:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
Many To Many Relationships
Like has many relationships,
"many to many" relationships may be created using the has
method:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->has(Role::factory()->count(3))
->create();
Pivot Table Attributes
If you need to define attributes that should be set on the pivot /
intermediate table linking the models, you may use the
hasAttached
method. This method accepts an array of pivot
table attribute names and values as its second argument:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->hasAttached(
Role::factory()->count(3),
['active' => true]
)
->create();
You may provide a closure based state transformation if your state change requires access to the related model:
$user = User::factory()
->hasAttached(
Role::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['name' => $user->name.' Role'];
}),
['active' => true]
)
->create();
If you already have model instances that you would like to be
attached to the models you are creating, you may pass the model
instances to the hasAttached
method. In this example, the
same three roles will be attached to all three users:
$roles = Role::factory()->count(3)->create();
$user = User::factory()
->count(3)
->hasAttached($roles, ['active' => true])
->create();
Using Magic Methods
For convenience, you may use Laravel's magic factory relationship
methods to define many to many relationships. For example, the following
example will use convention to determine that the related models should
be created via a roles
relationship method on the
User
model:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
Polymorphic Relationships
Polymorphic
relationships may also be created using factories. Polymorphic
"morph many" relationships are created in the same way as typical "has
many" relationships. For example, if a App\Models\Post
model has a morphMany
relationship with a
App\Models\Comment
model:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
Morph To Relationships
Magic methods may not be used to create morphTo
relationships. Instead, the for
method must be used
directly and the name of the relationship must be explicitly provided.
For example, imagine that the Comment
model has a
commentable
method that defines a morphTo
relationship. In this situation, we may create three comments that
belong to a single post by using the for
method
directly:
$comments = Comment::factory()->count(3)->for(
Post::factory(), 'commentable'
)->create();
Polymorphic Many To Many Relationships
Polymorphic "many to many" (morphToMany
/
morphedByMany
) relationships may be created just like
non-polymorphic "many to many" relationships:
use App\Models\Tag;
use App\Models\Video;
$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();
Of course, the magic has
method may also be used to
create polymorphic "many to many" relationships:
$videos = Video::factory()
->hasTags(3, ['public' => true])
->create();
Defining Relationships Within Factories
To define a relationship within your model factory, you will
typically assign a new factory instance to the foreign key of the
relationship. This is normally done for the "inverse" relationships such
as belongsTo
and morphTo
relationships. For
example, if you would like to create a new user when creating a post,
you may do the following:
use App\Models\User;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'user_id' => User::factory(),
'title' => $this->faker->title(),
'content' => $this->faker->paragraph(),
];
}
If the relationship's columns depend on the factory that defines it you may assign a closure to an attribute. The closure will receive the factory's evaluated attribute array:
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'user_id' => User::factory(),
'user_type' => function (array $attributes) {
return User::find($attributes['user_id'])->type;
},
'title' => $this->faker->title(),
'content' => $this->faker->paragraph(),
];
}
Running Seeders
If you would like to use database seeders
to populate your database during a feature test, you may invoke the
seed
method. By default, the seed
method will
execute the DatabaseSeeder
, which should execute all of
your other seeders. Alternatively, you pass a specific seeder class name
to the seed
method:
<?php
namespace Tests\Feature;
use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* Test creating a new order.
*
* @return void
*/
public function test_orders_can_be_created()
{
// Run the DatabaseSeeder...
$this->seed();
// Run a specific seeder...
$this->seed(OrderStatusSeeder::class);
// ...
// Run an array of specific seeders...
$this->seed([
OrderStatusSeeder::class,
TransactionStatusSeeder::class,
// ...
]);
}
}
Alternatively, you may instruct Laravel to automatically seed the
database before each test that uses the RefreshDatabase
trait. You may accomplish this by defining a $seed
property
on your base test class:
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
/**
* Indicates whether the default seeder should run before each test.
*
* @var bool
*/
protected $seed = true;
}
When the $seed
property is true
, the test
will run the Database\Seeders\DatabaseSeeder
class before
each test that uses the RefreshDatabase
trait. However, you
may specify a specific seeder that should be executed by defining a
$seeder
property on your test class:
use Database\Seeders\OrderStatusSeeder;
/**
* Run a specific seeder before each test.
*
* @var string
*/
protected $seeder = OrderStatusSeeder::class;
Available Assertions
Laravel provides several database assertions for your PHPUnit feature tests. We'll discuss each of these assertions below.
assertDatabaseCount
Assert that a table in the database contains the given number of records:
$this->assertDatabaseCount('users', 5);
assertDatabaseHas
Assert that a table in the database contains records matching the given key / value query constraints:
$this->assertDatabaseHas('users', [
'email' => 'sally@example.com',
]);
assertDatabaseMissing
Assert that a table in the database does not contain records matching the given key / value query constraints:
$this->assertDatabaseMissing('users', [
'email' => 'sally@example.com',
]);
assertDeleted
The assertDeleted
asserts that a given Eloquent model
has been deleted from the database:
use App\Models\User;
$user = User::find(1);
$user->delete();
$this->assertDeleted($user);
The assertSoftDeleted
method may be used to assert a
given Eloquent model has been "soft deleted":
$this->assertSoftDeleted($user);
assertModelExists
Assert that a given model exists in the database:
use App\Models\User;
$user = User::factory()->create();
$this->assertModelExists($user);
assertModelMissing
Assert that a given model does not exist in the database:
use App\Models\User;
$user = User::factory()->create();
$user->delete();
$this->assertModelMissing($user);