Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Foundry Laravel Package

zenstruck/foundry

Zenstruck Foundry is a Laravel-friendly factory and fixtures toolkit for building and persisting test data. Define model factories, create realistic related objects, seed databases, and write cleaner, faster tests with helpers for states, Faker, and repositories.

View on GitHub
Deep Wiki
Context7

Getting Started

First Steps

  1. Installation Run:

    composer require --dev zenstruck/foundry
    

    Add to composer.json under require-dev to ensure it’s excluded from production.

  2. Basic Setup Publish the config (optional but recommended for customization):

    php artisan vendor:publish --provider="Zenstruck\Foundry\FoundryServiceProvider"
    

    This generates config/foundry.php.

  3. First Fixture Create a fixture for a model (e.g., User):

    php artisan make:foundry User
    

    This generates database/foundry/UserFixture.php with a default state.

  4. Create and Persist Use the create() method in tests or Tinker:

    use Zenstruck\Foundry\ModelFactory;
    
    $user = UserFixture::new()->create();
    // or persist to DB:
    $user = UserFixture::new()->createPersisted();
    

Implementation Patterns

Core Workflows

  1. State-Based Fixtures Define reusable states in the fixture class:

    class UserFixture extends ModelFactory
    {
        protected function definition(): array
        {
            return [
                'name' => $this->faker->name,
                'email' => $this->faker->unique()->email,
            ];
        }
    
        public function admin(): static
        {
            return $this->state([
                'role' => 'admin',
                'is_active' => true,
            ]);
        }
    }
    

    Usage:

    $admin = UserFixture::new()->admin()->createPersisted();
    
  2. Relationships Use has() or hasMany() to define relationships:

    class PostFixture extends ModelFactory
    {
        protected function definition(): array
        {
            return [
                'title' => $this->faker->sentence,
                'user_id' => UserFixture::new()->create()->id,
            ];
        }
    
        public function withAuthor(): static
        {
            return $this->has('author', UserFixture::new()->admin());
        }
    }
    
  3. Collections Create multiple records at once:

    $users = UserFixture::new()->many(5)->create();
    // or persisted:
    $users = UserFixture::new()->many(3)->createPersisted();
    
  4. Customization via Callbacks Use afterCreating() for post-creation logic:

    public function withProfile(): static
    {
        return $this->afterCreating(function (User $user) {
            ProfileFixture::new()->create(['user_id' => $user->id]);
        });
    }
    

Integration Tips

  1. Laravel Testing Replace create() with createPersisted() in tests to ensure DB interactions:

    public function test_user_creation()
    {
        $user = UserFixture::new()->createPersisted();
        $this->assertDatabaseHas('users', ['email' => $user->email]);
    }
    
  2. Seeding Use fixtures in DatabaseSeeder for consistent test data:

    public function run()
    {
        UserFixture::new()->many(10)->createPersisted();
        PostFixture::new()->many(5)->createPersisted();
    }
    
  3. Dynamic Data Pass custom attributes to override defaults:

    $user = UserFixture::new()->create([
        'name' => 'John Doe',
        'email' => 'john@example.com',
    ]);
    
  4. Mocking External Services Use afterCreating() to mock API calls or queue jobs:

    $this->afterCreating(function (User $user) {
        Mockery::mock('overload', App\Services\Analytics::class)
            ->shouldReceive('track')
            ->once()
            ->with($user->id);
    });
    
  5. Unit Testing Without Doctrine Events In unit tests, disable doctrine events (e.g., observers) for performance:

    use Zenstruck\Foundry\TestCase;
    
    class UserTest extends TestCase
    {
        protected function setUp(): void
        {
            parent::setUp();
            $this->withoutDoctrineEvents(); // Disables events for the test
        }
    
        public function test_something()
        {
            $user = UserFixture::new()->create();
            // Test logic...
        }
    }
    

Gotchas and Tips

Common Pitfalls

  1. State Overrides States are not additive—each call to state() or *() (e.g., admin()) replaces previous states. Chain them carefully:

    // Wrong: 'role' will be 'user' (overridden)
    UserFixture::new()->admin()->user()->create();
    
    // Correct: Explicitly merge states
    UserFixture::new()->state(['role' => 'admin'])->user()->create();
    
  2. Persisted vs. In-Memory

    • create() returns an in-memory model (not persisted to DB).
    • createPersisted() triggers events (e.g., created) and validates.
    • Use refresh() to reload a persisted model from DB:
      $user = UserFixture::new()->createPersisted();
      $user->refresh()->load('profile'); // Reload with relations
      
  3. Faker Conflicts If using multiple Faker locales, ensure consistency:

    // Set locale globally in config/foundry.php
    'faker_locale' => 'en_US',
    

    Or override per fixture:

    public function __construct()
    {
        $this->faker = Faker::create('fr_FR');
    }
    
  4. Circular Relationships Avoid infinite loops with recursive has() calls. Use afterCreating() for complex setups:

    // Bad: Circular dependency
    class CategoryFixture extends ModelFactory {
        public function withParent(): static {
            return $this->has('parent', CategoryFixture::new());
        }
    }
    
    // Good: Break cycles with callbacks
    $category = CategoryFixture::new()->createPersisted();
    $parent = CategoryFixture::new()->createPersisted();
    $category->parent()->associate($parent);
    
  5. Doctrine Events in Unit Tests The withoutDoctrineEvents() method now correctly does nothing in unit tests (previously, it might have caused unexpected behavior). Ensure you’re using it intentionally in integration tests:

    // Only use in integration tests, not unit tests
    $this->withoutDoctrineEvents();
    

Debugging Tips

  1. Inspect States Dump the final state before creation:

    $fixture = UserFixture::new()->admin()->withProfile();
    dd($fixture->getState());
    
  2. Check Events Listen for fixture events in tests:

    Foundry::on('creating', function ($fixture) {
        dump($fixture->getState());
    });
    
  3. Clear Cached Factories If changes to fixtures aren’t reflected, clear the cache:

    php artisan cache:clear
    php artisan config:clear
    

Extension Points

  1. Custom Factory Classes Extend ModelFactory for shared logic:

    class BaseUserFixture extends ModelFactory
    {
        protected function commonDefinition(): array
        {
            return ['is_active' => true];
        }
    
        protected function definition(): array
        {
            return array_merge($this->commonDefinition(), [
                'name' => $this->faker->name,
            ]);
        }
    }
    
  2. Dynamic Attributes Use afterInstantiated() to compute attributes:

    public function withHashedPassword(): static
    {
        return $this->afterInstantiated(function (User $user) {
            $user->password = bcrypt('password');
        });
    }
    
  3. Database-Specific Logic Override persist() for custom DB logic (e.g., UUIDs):

    protected function persist(Model $model): Model
    {
        $model->id = Str::uuid()->toString();
        $model->saveOrFail();
        return $model;
    }
    
  4. Testing Helpers Create a trait for reusable test setups:

    trait CreatesUsers
    {
        protected function createTestUser(): User
        {
            return UserFixture::new()->createPersisted();
        }
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope