Files
2025-11-29 17:58:52 +08:00

6.8 KiB

Framework Make Story

Génère une Story Foundry pour fixtures de tests.

Vue d'ensemble

Cette skill crée une Story Zenstruck Foundry pour orchestrer la création de fixtures de tests cohérentes et réutilisables.

Caractéristiques

Classe Story générée

  • Extends Story
  • Implements StoryInterface
  • Classe final
  • Méthode build() avec scénarios
  • Utilise Factories pour créer instances
  • Scénarios prédéfinis

Classe AppStory

  • Point d'entrée global
  • Attribut #[AsFixture(name: 'main')]
  • Charge toutes les stories
  • Gère les dépendances entre stories

Utilisation

Use skill framework:make:story

Vous serez invité à fournir le nom de l'entité.

Exemple d'utilisation

EntityName: Product

Génère :

// src/Story/ProductStory.php
final class ProductStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // Produit par défaut
        ProductFactory::createOne();

        // Produits avec IDs spécifiques
        ProductFactory::new()
            ->withSpecificId('01234567-89ab-cdef-0123-456789abcdef')
            ->create();

        // Plusieurs produits
        ProductFactory::createMany(10);
    }
}

// src/Story/AppStory.php (updated)
#[AsFixture(name: 'main')]
final class AppStory extends Story implements StoryInterface
{
    public function build(): void
    {
        ProductStory::load();
        // ... autres stories
    }
}

Structure créée

src/
└── Story/
    ├── {EntityName}Story.php
    └── AppStory.php

Prérequis

  • L'entité doit exister
  • La Factory doit exister (créée automatiquement si absente)
  • StoryInterface doit exister dans Contracts
  • Zenstruck Foundry installé

Usage dans les tests

Tests unitaires

use App\Story\ProductStory;

final class ProductServiceTest extends TestCase
{
    protected function setUp(): void
    {
        ProductStory::load();
    }

    public function testCalculateTotalPrice(): void
    {
        $products = ProductFactory::repository()->findAll();
        $service = new ProductService();

        $total = $service->calculateTotal($products);

        self::assertGreaterThan(0, $total);
    }
}

Tests fonctionnels

use App\Story\AppStory;

final class ProductControllerTest extends WebTestCase
{
    protected function setUp(): void
    {
        AppStory::load();
    }

    public function testListProducts(): void
    {
        $client = static::createClient();
        $client->request('GET', '/products');

        self::assertResponseIsSuccessful();
        self::assertSelectorExists('.product-item');
    }
}

Fixtures Doctrine

# Charger toutes les fixtures
php bin/console doctrine:fixtures:load --append

# Ou juste AppStory via attribut #[AsFixture]

Enrichissement (principe YAGNI)

Scénarios métier

final class ProductStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // Scénario : catalogue actif
        ProductFactory::new()
            ->active()
            ->createMany(20);

        // Scénario : produits en promotion
        ProductFactory::new()
            ->active()
            ->createMany(5, ['price' => 9.99]);

        // Scénario : rupture de stock
        ProductFactory::new()
            ->outOfStock()
            ->createMany(3);

        // Scénario : nouveau produit vedette
        ProductFactory::createOne([
            'name' => 'Featured Product',
            'price' => 199.99,
            'stock' => 100,
            'isActive' => true,
        ]);
    }
}

Avec relations

final class ProductStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // Charger dépendances
        CategoryStory::load();

        // Récupérer catégories
        $electronics = CategoryFactory::find(['name' => 'Electronics']);
        $books = CategoryFactory::find(['name' => 'Books']);

        // Produits par catégorie
        ProductFactory::new()
            ->inCategory($electronics)
            ->createMany(10);

        ProductFactory::new()
            ->inCategory($books)
            ->createMany(15);
    }
}

États nommés

final class ProductStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // États nommés pour réutilisation
        $this->addState('premium', ProductFactory::createOne([
            'name' => 'Premium Product',
            'price' => 999.99,
        ]));

        $this->addState('cheap', ProductFactory::createOne([
            'name' => 'Cheap Product',
            'price' => 9.99,
        ]));

        $this->addState('test_product', ProductFactory::createOne([
            'id' => Uuid::fromString('01234567-89ab-cdef-0123-456789abcdef'),
        ]));
    }
}

// Usage
$premium = ProductStory::load()->get('premium');
$cheap = ProductStory::load()->get('cheap');

Scénarios complexes

final class OrderStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // Charger dépendances
        UserStory::load();
        ProductStory::load();

        $users = UserFactory::repository()->findAll();
        $products = ProductFactory::repository()->findAll();

        // Commandes par utilisateur
        foreach ($users as $user) {
            OrderFactory::createOne([
                'user' => $user,
                'items' => array_slice($products, 0, rand(1, 5)),
            ]);
        }

        // Commande de test spécifique
        $this->addState('test_order', OrderFactory::createOne([
            'user' => UserStory::load()->get('test_user'),
            'items' => [ProductStory::load()->get('test_product')],
            'status' => 'pending',
        ]));
    }
}

AppStory orchestration

#[AsFixture(name: 'main')]
final class AppStory extends Story implements StoryInterface
{
    public function build(): void
    {
        // Ordre important : dépendances d'abord
        CategoryStory::load();
        ProductStory::load();
        UserStory::load();
        OrderStory::load();
        ReviewStory::load();
    }
}

Configuration Foundry

# config/packages/zenstruck_foundry.yaml
when@dev:
    zenstruck_foundry:
        auto_refresh_proxies: false

when@test:
    zenstruck_foundry:
        auto_refresh_proxies: false
        make_factory:
            default_namespace: 'App\Factory'

Commandes utiles

# Charger fixtures
php bin/console doctrine:fixtures:load --append

# Purger DB puis charger
php bin/console doctrine:fixtures:load

# Dans tests
AppStory::load();
ProductStory::load();

Principes Elegant Objects appliqués

  • Classe finale
  • Implements StoryInterface
  • Méthode build() claire et concise
  • Scénarios nommés explicitement
  • DRY : fixtures centralisées
  • États nommés pour réutilisation