Search code examples
phplaravelphpunitlaravel-factoryphp-faker

How to change default values of nested entities created with factories?


My data model looks like this:

a diagram with three entities

An Order contains between 1 and N Products, a Product has a single Category.

I have 3 Factories, one for each entity: OrderFactory, ProductFactory, CategoryFactory. In them, I setup default data:

namespace Database\Factories;

use App\Category;
use App\Product;
use Illuminate\Database\Eloquent\Factories\Factory;

class ProductFactory extends Factory
{
    protected $model = Product::class;

    public function definition()
    {
        $name = $this->faker->realText(20);
        return [
            'category_id' => Category::factory(),
            'reference' => str_replace(' ', '', $name),
            'name' => $name,
            'price' => $this->faker->randomFloat(2, 1.00, 200.00),
        ];
    }
}

namespace Database\Factories;

use App\Category;
use Illuminate\Database\Eloquent\Factories\Factory;

class CategoryFactory extends Factory
{
    protected $model = Category::class;

    public function definition()
    {
        return [
            'label' => $this->faker->realText(20),
            'description' => $this->faker->text,
        ];
    }
}

I want for one particular test to create an Order containing 5 products of a Category named my specific category label. I tried to do it like so:

$order = Order::factory()
    ->state([
        'number' => 014789012,
    ])
    ->has(
        Product::factory()
            ->count(3)
            ->has(
                Category::factory()->state([
                    'label' => 'my specific category label',
                ])
            )
    )
    ->create();

But the Category->label is not edited and remains the default one defined in CategoryFactory. Why?


Solution

  • I made a mistake by using has() instead of for() on my Product <=> Category relation: as it is a OneToMany relationship, we need to use for(), like so:

    $order = Order::factory()
        ->state([
            'number' => 014789012,
        ])
        ->has(
            Product::factory()
                ->count(3)
                ->for(
                    Category::factory()->state([
                        'label' => 'my specific category label',
                    ])
                )
        )
        ->create();