Speeding Up Laravel Tests with tmpfs (MySQL in RAM)
If your Laravel test suite is painfully slow and does a lot of database work (multi-tenancy, migrations, seeding), the bottleneck is almost…
In Laravel, a Factory is a class dedicated to generating instances of your Eloquent models. They solve a fundamental problem: your application needs data to function, especially during testing and development, and creating that data manually is tedious, repetitive, and error-prone.
Think of it like a manufacturing assembly line for your data.
User model, the blueprint might specify that every user must have a name, a unique email address, and a hashed password. This blueprint is the definition() method in your factory class."Build one standard product." -> User::factory()->create()"Build 50 standard products." -> User::factory()->count(50)->create()"Build one standard product, but make sure this one is an administrator." -> User::factory()->create(['is_admin' => true])In essence, a Laravel Factory provides a centralized, repeatable, and fluent API for constructing and persisting model objects, primarily for automated tests and database seeding.
You create a factory using an Artisan command. For a Post model, you run:
php artisan make:factory PostFactory --model=Post
This generates database/factories/PostFactory.php, which looks like this:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = \App\Models\Post::class; // Links this factory to the Post model
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
// This is the core blueprint for a Post model.
return [
// 'database_column_name' => value,
];
}
}
definition() MethodThis method is the heart of the factory. You define the default attributes for your model here. To generate realistic fake data, Laravel integrates the Faker library.
Here is a filled-out definition() for a Post model:
public function definition(): array
{
return [
'title' => $this->faker->sentence(6), // Generates a random sentence with 6 words.
'content' => $this->faker->paragraphs(3, true), // Generates 3 paragraphs as a single string.
'is_published' => $this->faker->boolean(75), // 75% chance of being `true`.
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'), // A random date in the last year.
];
}
$this->faker gives you access to dozens of data generators for names, addresses, text, numbers, dates, and more.
create() vs. make()This is a critical distinction:
Post::factory()->create(): Creates a model instance AND persists it to the database. This is what you use in 99% of tests and all database seeders.
Post::factory()->make(): Creates a model instance but does NOT save it to the database. The object only exists in memory. This is useful for testing logic that happens before a model is saved, like validation requests.
// Generate and save one Post to the database with default attributes
$post = Post::factory()->create();
// Generate and save 10 Posts
$posts = Post::factory()->count(10)->create();
// Generate one Post, but override the default 'title'
$specificPost = Post::factory()->create([
'title' => 'My Specific Test Title',
]);
// Generate an in-memory instance without saving it
$unsavedPost = Post::factory()->make();
This is where factories become extremely powerful. If a Post belongs to a User, you can define that relationship directly in the factory.
In PostFactory.php:
use App\Models\User;
public function definition(): array
{
return [
'user_id' => User::factory(), // <-- This is the key part
'title' => $this->faker->sentence(),
'content' => $this->faker->paragraph(),
];
}
When you now run Post::factory()->create(), Laravel's factory system will:
user_id needs a User model.UserFactory to create a new User.User.user_id on the Post being created.User and the Post to the database.Generating models with their relationships:
// Create a User and 5 posts that belong to that user.
// Assumes a `posts()` relationship method exists on the User model.
$userWithPosts = User::factory()
->hasPosts(5) // Uses the magic "has<RelationshipName>" method
->create();
// Alternatively, a more explicit syntax:
$userWithPosts = User::factory()
->has(Post::factory()->count(5))
->create();
States are predefined modifications to your factory's default definition. They are used to represent common variations of a model. For example, a post can be a "draft" or "published".
In PostFactory.php, define a state:
class PostFactory extends Factory
{
// ... definition() method ...
/**
* Define a "published" state for the post.
*/
public function published(): static
{
return $this->state(fn (array $attributes) => [
'is_published' => true,
'published_at' => now(),
]);
}
}
Using the state:
This makes your tests and seeders much more readable.
// Create a default post (is_published is random)
Post::factory()->create();
// Create a post that is explicitly published
Post::factory()->published()->create();
// Create 5 published posts and 10 draft posts
Post::factory()->count(5)->published()->create();
Post::factory()->count(10)->create(['is_published' => false]); // a draft via overriding
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com