6.8 KiB
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