๐ฅ
Laravel Factory Enhanced
Bring the magic of eloquent relationships into the Laravel Factory.
Traditionally if you wanted to factory a team with some users, you'd have to manually create the individual team and users and then tie them together afterwards. This can easily lead to very verbose tests.
Laravel 7.x and earlier
$team = factory(Team::class)->create();
$users = factory(User::class)->times(2)->create();
foreach ($users as $user) {
factory(TeamMember::class)->create([
'team_id' => $team,
'user_id' => $user,
'role' => 'admin'
]);
}
Laravel 8.0 and later
$team = Team::factory()
->hasAttached(
User::factory()->count(2),
['active' => true]
)
->create();
Laravel Factory Enhanced
$team = Team::factory()
->with(2, 'users', ['pivot.role' => 'admin'])
->create();
Installation
You may install the package via composer
composer require makeabledk/laravel-factory-enhanced
Versions
Laravel 8+ class based factories
Version 4 and later of this package is compatible with the new class-based syntax introduced with Laravel 8.
Pre-Laravel 7 factories
Version 3 and earlier of this package is compatible with the legacy $factory->define()
syntax. Please find docs here v3 documentation:
Upgrade guide to v4
The majority of the refactoring needed to upgrade to v4 of this package, lies in rewriting factories to be compatible with Laravel class-based factories.
If you use Laravel Shift when upgrading to Laravel 8, a lot of this work will be automated, and you will be well on you way.
Rewriting to class based factories
Please use Laravel Shift for upgrading Laravel versions, or refer to the official documentation on how to write factories using the class-based approach.
Applying states
Replace all occurrences of ->state('some-state')
with ->someState()
in your test suite.
Presets
The concept of presets which was introduced by this package may now simply be rewritten to states.
As such, replace all occurrences of ->preset('some-preset')
with ->somePreset()
in your test suite.
Times method
Replace all occurrences of ->times(x)
with ->count(x)
in your test suite.
Factory helper syntax
This change is completely optional. If you wish, you may change all occurrence of factory(SomeModel::class)
to SomeModel::factory()
in your test suite.
If you choose to do so, remember to add use \Makeable\LaravelFactory\Factory;
to all models.
Other breaking changes
The odds()
method has been removed from the Factory instance.
Usage
Once the package is installed, your factories should extend Makeable\LaravelFactory\Factory
rather than the native Laravel Factory
class.
Additionally, please make sure to use the corresponding Makeable\LaravelFactory\HasFactory
trait on your models.
For example:
app/Models/User.php
<?php
namespace App\Models;
use Makeable\LaravelFactory\HasFactory;
class User
{
use HasFactory;
// ...
}
database/factories/UserFactory.php
<?php
namespace Database\Factories;
use Makeable\LaravelFactory\Factory;
class UserFactory extends Factory
{
public function definition()
{
return [
// ...
];
}
}
You may now use all the native Laravel features you are used to, along with the additional functionality this package provides.
If you're not familiar with Laravel factories, please refer to the official documentation: https://laravel.com/docs/master/database-testing
Simple relationships
You may use the enhanced factory to create any additional relationships defined on the model.
Example: A Server
model with a sites()
relationship (has-many)
Server::factory()->with(3, 'sites')->create();
Note that you may still use any native functionality that you are used to, such as states and attributes:
Server::factory()->online()->with(3, 'sites')->create([
'name' => 'production-1'
]);
Multiple relationships
You are free to specify multiple relationships on the same factory.
Given our previous Server
model has another relationship called team
(belongs-to), you may do:
Server::factory()
->with('team')
->with(3, 'sites')
->create();
Now you would have 1 team
that has 1 server
which has 3 sites
.
Nested relationships
Moving on to the more advanced use-cases, you may also do nested relationships.
For instance we could rewrite our example from before using nesting:
Team::factory()
->with(3, 'servers.sites')
->create();
Please note that the count '3' applies to the final relationship, in this case sites
.
If you are wanting 2 servers each of which has 3 sites, you may write it as following:
Team::factory()
->with(2, 'servers')
->with(3, 'servers.sites')
->create();
States in relationships
Just as you may specify pre-defined states on the factoring model (see official documentation), you may also apply the very same states on the relation.
Team::factory()
->with(2, 'online', 'servers')
->create();
You may finding yourself wanting a relationship in multiple states. In this case you may use the andWith()
method.
Team::factory()
->with(2, 'online', 'servers')
->andWith(1, 'offline', 'servers')
->create();
By using the andWith
we will create a 'clean cut', so that no further calls to with
can interfere with relations specified prior to the andWith
.
In the above example any further nesting of relations will apply to the 'offline' server.
Team::factory()
->with(2, 'online', 'servers')
->andWith(1, 'offline', 'servers')
->with(3, 'servers.sites')
->create();
The above example will create 1x team that has
- 2x online servers
- 1x offline servers with 3 sites
Filling attributes in relationships
You may fill attributes on a relationship by passing them as an argument to the with()
method.
factory(Team::class)
->with(2, 'servers', ['name' => 'laravel.com'])
->create();
If the relation is a belongs-to-many relationship, you may also fill attributes on the pivot model by prefixing the attribute name with pivot.
.
Team::factory()
->with(2, 'users', ['pivot.role' => 'admin'])
->create();
Using apply()
All of the above examples of how you might configure a relationship using the with()
method, can also be applied on the base model using the apply()
method.
For example:
Server::factory()
->apply(2, 'online', ['name' => 'laravel.com'])
->with(3, 'mysql', 'databases')
->create();
This would create 2 online servers each with 3 mysql databases.
In fact, by using the HasFactory
trait from this package, you can even pass these arguments to the ::factory()
method itself:
Server::factory(2, 'online', ['name' => 'laravel.com'])
->with(3, 'mysql', 'databases')
->create();
Using closures for customization
In addition to passing count and state directly into the with
function, you may also pass a closure that will receive the FactoryBuilder
instance directly.
In the closure you can do everything you are used to on the FactoryBuilder
- including nesting further relationships should you wish to.
Team::factory()
->with('servers', fn (ServerFactory $servers) => $servers
->count(2)
->active()
->with(3, 'sites')
)
->create();
Factoring models with no defined factory
Traditionally trying to use Model::factory()
on a model with no defined factory would throw an exception. Not anymore!
After installing this package, you are completely free to use the static Model::factory()
method on any Eloquent model that uses the Makeable\LaravelFactory\HasFactory
trait.
Furthermore, this package brings back the good old factory(Model::class)
helper function which you may use on any model, whether or not the model has a defined factory or uses the HasFactory
trait.
Example
factory(2, Server::class)->with(1, 'sites')->create();
Available methods
These are the provided methods on the Factory
instance in addition to the core methods.
- apply
- fill
- fillPivot (only applicable on BelongsToMany
- pipe
- tap
- with
- andWith
Testing
You can run the tests with:
composer test
Contributing
We are happy to receive pull requests for additional functionality. Please see CONTRIBUTING for details.
Credits
License
Attribution-ShareAlike 4.0 International. Please see License File for more information.