HTTP tests — $this->get(), post(), assertStatus(), assertJson()
Concept
Model Factories generate realistic test data. They define default attribute values, states, sequences, relationships, and afterCreating callbacks. Factories eliminate repetitive test setup code and make tests readable.
Creating: php artisan make:factory PostFactory --model=Post. Generates database/factories/PostFactory.php.
definition() method: Returns default attributes array. Uses Faker via $this->faker.
States: Method that modifies the factory state. Named with the semantic meaning:
public function published(): static
{
return $this->state(['status' => 'published', 'published_at' => now()]);
}for() method: Associate a belongs-to relationship. Post::factory()->for($user) sets user_id. Post::factory()->for(User::factory()) also creates the user.
has() method: Create has-many relationships. User::factory()->has(Post::factory()->count(3)) creates user with 3 posts.
Magic methods: Post::factory()->hasPosts(3) works for hasXxx() — calls the has() method for the posts relationship.
afterCreating(Closure): Run code after the model is created. Useful for setting up pivot data, dispatching events, etc.
sequence(array ...$sequences): Alternate between values: Post::factory()->count(3)->sequence(['status' => 'draft'], ['status' => 'published']).
Faker integration: $this->faker->name(), $this->faker->email(), $this->faker->paragraph(), $this->faker->numberBetween(1, 100), etc.
Code Example
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class PostFactory extends Factory
{
public function definition(): array
{
$title = $this->faker->sentence(rand(4, 8));
return [
'user_id' => User::factory(), // auto-creates a user
'title' => $title,
'slug' => Str::slug($title),
'body' => $this->faker->paragraphs(5, true),
'status' => 'draft',
'published_at' => null,
'view_count' => 0,
];
}
// States
public function published(): static
{
return $this->state([
'status' => 'published',
'published_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
]);
}
public function trashed(): static
{
return $this->state(['deleted_at' => now()]);
}
public function popular(): static
{
return $this->state(['view_count' => $this->faker->numberBetween(1000, 50000)]);
}
}
// Usage in tests
$draftPost = Post::factory()->create();
$publishedPost = Post::factory()->published()->create();
$popularPost = Post::factory()->published()->popular()->create();
// Sequences — alternate status
$posts = Post::factory()
->count(6)
->sequence(
['status' => 'draft'],
['status' => 'published'],
['status' => 'archived']
)
->create();
// 6 posts: draft, published, archived, draft, published, archived
// Relationships
$author = User::factory()->create();
$userPosts = Post::factory()->count(3)->for($author)->published()->create();
// has() — user with 5 posts
$userWithPosts = User::factory()
->has(Post::factory()->count(5)->published())
->create();