Skip to main content
Laravel Modular provides automatic factory loading and namespace resolution for model factories within modules, making it easy to generate test data.

Creating Factories

Create a factory for a module model using the --module flag:
php artisan make:factory PostFactory --module=blog
This creates a factory in:
app-modules/blog/database/factories/PostFactory.php
Create a model with its factory in one command:
php artisan make:model Post --module=blog --factory
Or use the shorthand: php artisan make:model Post --module=blog -f

Factory Structure

Module factories follow the standard Laravel factory pattern:
app-modules/blog/database/factories/PostFactory.php
<?php

namespace Modules\Blog\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Blog\Models\Post;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        $title = fake()->sentence();
        
        return [
            'title' => $title,
            'slug' => Str::slug($title),
            'excerpt' => fake()->paragraph(),
            'content' => fake()->paragraphs(5, true),
            'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}

Using Factories

In Tests

Use factories in your tests to create test data:
app-modules/blog/tests/PostTest.php
<?php

namespace Modules\Blog\Tests;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\Blog\Models\Post;

class PostTest extends TestCase
{
    use RefreshDatabase;
    
    public function test_can_create_post()
    {
        $post = Post::factory()->create();
        
        $this->assertDatabaseHas('posts', [
            'id' => $post->id,
        ]);
    }
    
    public function test_can_create_multiple_posts()
    {
        $posts = Post::factory()->count(5)->create();
        
        $this->assertCount(5, $posts);
        $this->assertEquals(5, Post::count());
    }
}

In Seeders

Use factories in seeders:
app-modules/blog/database/seeders/PostSeeder.php
<?php

namespace Modules\Blog\Database\Seeders;

use Illuminate\Database\Seeder;
use Modules\Blog\Models\Post;

class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::factory()->count(50)->create();
    }
}

In Tinker

Use factories in php artisan tinker:
>>> use Modules\Blog\Models\Post;
>>> Post::factory()->count(10)->create();

Factory States

Define different states for your factories:
app-modules/blog/database/factories/PostFactory.php
<?php

namespace Modules\Blog\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Blog\Models\Post;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        return [
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
            'published_at' => null,
        ];
    }
    
    /**
     * Indicate that the post is published.
     */
    public function published(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
        ]);
    }
    
    /**
     * Indicate that the post is a draft.
     */
    public function draft(): static
    {
        return $this->state(fn (array $attributes) => [
            'published_at' => null,
        ]);
    }
    
    /**
     * Indicate that the post is featured.
     */
    public function featured(): static
    {
        return $this->state(fn (array $attributes) => [
            'featured' => true,
        ]);
    }
}

Using States

// Create published posts
$posts = Post::factory()->published()->count(10)->create();

// Create draft posts
$drafts = Post::factory()->draft()->count(5)->create();

// Create featured, published posts
$featured = Post::factory()->published()->featured()->count(3)->create();

// Chain multiple states
$post = Post::factory()
    ->published()
    ->featured()
    ->create(['title' => 'Special Post']);

Factory Relationships

Define relationships within factories:

Belongs To

app-modules/blog/database/factories/PostFactory.php
<?php

namespace Modules\Blog\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Blog\Models\Post;
use Modules\Blog\Models\Category;
use App\Models\User;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        return [
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
            'user_id' => User::factory(),
            'category_id' => Category::factory(),
            'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}
Now creating a post automatically creates related models:
// Creates a post, a user, and a category
$post = Post::factory()->create();

// Use existing user
$user = User::find(1);
$post = Post::factory()->create(['user_id' => $user->id]);

// Or use the for() method
$post = Post::factory()->for($user)->create();

Has Many

Create models with related children:
// Create a post with 5 comments
$post = Post::factory()
    ->has(Comment::factory()->count(5))
    ->create();

// Using the magic method
$post = Post::factory()
    ->hasComments(5)
    ->create();

// With custom attributes
$post = Post::factory()
    ->hasComments(3, ['approved' => true])
    ->create();

Many to Many

For many-to-many relationships:
// Create a post with tags
$post = Post::factory()
    ->hasAttached(Tag::factory()->count(3))
    ->create();

// With pivot data
$post = Post::factory()
    ->hasAttached(
        Tag::factory()->count(3),
        ['created_at' => now()]
    )
    ->create();

Factory Callbacks

Use callbacks to perform actions after model creation:
app-modules/blog/database/factories/PostFactory.php
public function definition(): array
{
    return [
        'title' => fake()->sentence(),
        'content' => fake()->paragraphs(3, true),
    ];
}

/**
 * Configure the model factory.
 */
public function configure(): static
{
    return $this->afterCreating(function (Post $post) {
        // Generate a thumbnail after creating
        $post->generateThumbnail();
    });
}

AfterMaking and AfterCreating

public function configure(): static
{
    return $this
        ->afterMaking(function (Post $post) {
            // Runs after make() - model not yet in database
            $post->slug = Str::slug($post->title);
        })
        ->afterCreating(function (Post $post) {
            // Runs after create() - model is in database
            $post->generateSearchIndex();
        });
}

Automatic Factory Resolution

Laravel Modular automatically resolves factory names for module models. This happens through the DatabaseFactoryHelper:
// When you call this:
Post::factory()

// Laravel Modular automatically finds:
Modules\Blog\Database\Factories\PostFactory
This works because of automatic namespace resolution configured in the module’s composer.json:
composer.json
{
  "autoload": {
    "psr-4": {
      "Modules\\Blog\\Database\\Factories\\": "database/factories/"
    }
  }
}
Factory autoloading is registered automatically when the module is installed via Composer.

Nested Model Factories

Organize factories in subdirectories for complex modules:
app-modules/blog/
└── database/
    └── factories/
        ├── PostFactory.php
        ├── CategoryFactory.php
        └── Media/
            ├── ImageFactory.php
            └── VideoFactory.php
Namespace them accordingly:
app-modules/blog/database/factories/Media/ImageFactory.php
<?php

namespace Modules\Blog\Database\Factories\Media;

use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Blog\Models\Media\Image;

class ImageFactory extends Factory
{
    protected $model = Image::class;
    
    // ...
}

Testing with Factories

Basic Tests

public function test_post_has_required_fields()
{
    $post = Post::factory()->create([
        'title' => 'Test Title',
    ]);
    
    $this->assertEquals('Test Title', $post->title);
    $this->assertNotNull($post->slug);
    $this->assertNotNull($post->content);
}

Testing Relationships

public function test_post_belongs_to_user()
{
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();
    
    $this->assertEquals($user->id, $post->user_id);
    $this->assertTrue($post->user->is($user));
}

public function test_post_has_comments()
{
    $post = Post::factory()
        ->hasComments(3)
        ->create();
    
    $this->assertCount(3, $post->comments);
}

Testing States

public function test_published_scope_only_returns_published_posts()
{
    Post::factory()->published()->count(5)->create();
    Post::factory()->draft()->count(3)->create();
    
    $published = Post::published()->get();
    
    $this->assertCount(5, $published);
    $this->assertTrue($published->every(fn($post) => $post->isPublished()));
}

Seeding with Factories

Use factories in database seeders:
app-modules/blog/database/seeders/DatabaseSeeder.php
<?php

namespace Modules\Blog\Database\Seeders;

use Illuminate\Database\Seeder;
use Modules\Blog\Models\Post;
use Modules\Blog\Models\Category;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // Create categories
        $categories = Category::factory()->count(5)->create();
        
        // Create users
        $users = User::factory()->count(10)->create();
        
        // Create posts for each user
        $users->each(function ($user) use ($categories) {
            Post::factory()
                ->count(rand(3, 8))
                ->for($user)
                ->for($categories->random())
                ->create();
        });
        
        // Create some featured posts
        Post::factory()
            ->published()
            ->featured()
            ->count(5)
            ->create();
    }
}
Run the seeder:
php artisan db:seed --class="Modules\Blog\Database\Seeders\DatabaseSeeder"

Best Practices

Make test data realistic with Faker:
'title' => fake()->sentence(),
'email' => fake()->unique()->safeEmail(),
'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
Create states for common variations:
Post::factory()->published()->create();
Post::factory()->draft()->create();
Post::factory()->featured()->create();
Create variations with sequences:
Post::factory()
    ->count(3)
    ->sequence(
        ['status' => 'draft'],
        ['status' => 'published'],
        ['status' => 'archived'],
    )
    ->create();
Update factories when you change models:
// When you add a new required field to posts table
// Update the factory definition immediately
public function definition(): array
{
    return [
        // ... existing fields
        'new_required_field' => fake()->word(),
    ];
}

Next Steps

Module Migrations

Learn about database migrations in modules

Module Components

Create models and other components

Build docs developers (and LLMs) love