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 supercharges Laravel/Symfony testing with fluent model factories, fixtures, and story-based data builders. Create, persist, and customize entities easily, manage relations, and write cleaner, faster tests with powerful helpers and states.

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);
    });
    

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);
    

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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport