Skip to content

Commit fddd6d5

Browse files
committed
feat(story as fixture): if only one fixture, load it by default
1 parent 23846e5 commit fddd6d5

13 files changed

+125
-42
lines changed

config/persistence.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
->arg('$kernel', service('kernel'))
2525
->tag('console.command', [
2626
'command' => 'foundry:load-stories',
27-
'aliases' => ['foundry:load-fixtures'],
27+
'aliases' => ['foundry:load-fixtures', 'foundry:load-fixture', 'foundry:load-story'],
2828
'description' => 'Load stories which are marked with #[AsFixture] attribute.',
2929
])
3030
;

docs/index.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,7 @@ once. To do this, wrap the operations in a ``flush_after()`` callback:
11601160
TagFactory::createMany(200); // instantiated/persisted but not flushed
11611161
}); // single flush
11621162

1163-
The ``flush_after()`` function forwards the callbacks return, in case you need to use the objects in your tests:
1163+
The ``flush_after()`` function forwards the callback's return, in case you need to use the objects in your tests:
11641164

11651165
::
11661166

@@ -2411,6 +2411,10 @@ Mark with the attribute ``#[AsFixture]`` the stories your want to be loaded by t
24112411

24122412
``bin/console foundry:load-stories category`` will now load the story ``CategoryStory`` in your database.
24132413

2414+
.. note::
2415+
2416+
If only a single story exists, you can omit the argument and just call ``bin/console foundry:load-stories`` to load it.
2417+
24142418
You can also load stories by group, by using the ``groups`` option:
24152419

24162420
::
@@ -2425,7 +2429,7 @@ You can also load stories by group, by using the ``groups`` option:
24252429

24262430
``bin/console foundry:load-stories all-stories`` will load both stories ``CategoryStory`` and ``PostStory``.
24272431

2428-
.. note::
2432+
.. tip::
24292433

24302434
It is possible to call a story inside another story, by using `OtherStory::load();`. Because the stories are only
24312435
loaded once, it will work regardless of the order of the stories.

src/Attribute/AsFixture.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
namespace Zenstruck\Foundry\Attribute;
1515

16-
use Zenstruck\Foundry\Story;
17-
1816
/**
1917
* @author Nicolas PHILIPPE <[email protected]>
2018
*/

src/Command/LoadStoryCommand.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Zenstruck\Foundry\Command;
1313

14+
use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticDriver;
1415
use Symfony\Component\Console\Command\Command;
1516
use Symfony\Component\Console\Exception\InvalidArgumentException;
1617
use Symfony\Component\Console\Exception\LogicException;
@@ -19,14 +20,9 @@
1920
use Symfony\Component\Console\Input\InputOption;
2021
use Symfony\Component\Console\Output\OutputInterface;
2122
use Symfony\Component\Console\Style\SymfonyStyle;
22-
use Symfony\Component\DependencyInjection\ServiceLocator;
2323
use Symfony\Component\HttpKernel\KernelInterface;
24-
use Zenstruck\Foundry\Configuration;
25-
use Zenstruck\Foundry\Exception\PersistenceNotAvailable;
2624
use Zenstruck\Foundry\Persistence\ResetDatabase\BeforeFirstTestResetter;
27-
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;
2825
use Zenstruck\Foundry\Story;
29-
use Zenstruck\Foundry\Tests\Fixture\TestKernel;
3026

3127
/**
3228
* @author Nicolas PHILIPPE <[email protected]>
@@ -41,8 +37,7 @@ public function __construct(
4137
/** @var iterable<BeforeFirstTestResetter> */
4238
private iterable $databaseResetters,
4339
private KernelInterface $kernel,
44-
)
45-
{
40+
) {
4641
parent::__construct();
4742
}
4843

@@ -56,7 +51,7 @@ protected function configure(): void
5651

5752
protected function execute(InputInterface $input, OutputInterface $output): int
5853
{
59-
if (count($this->stories) === 0) {
54+
if (0 === \count($this->stories)) {
6055
throw new LogicException('No story as fixture available: add attribute #[AsFixture] to your story classes before running this command.');
6156
}
6257

@@ -69,14 +64,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6964
$stories = [];
7065

7166
if (null === ($name = $input->getArgument('name'))) {
72-
$storyNames = array_keys($this->stories);
73-
if (count($this->groupedStories) > 0) {
74-
$storyNames[] = '(choose a group of stories...)';
67+
if (1 === \count($this->stories)) {
68+
$name = \array_keys($this->stories)[0];
69+
} else {
70+
$storyNames = \array_keys($this->stories);
71+
if (\count($this->groupedStories) > 0) {
72+
$storyNames[] = '(choose a group of stories...)';
73+
}
74+
$name = $io->choice('Choose a story to load:', $storyNames);
7575
}
76-
$name = $io->choice('Choose a story to load:', $storyNames);
7776

7877
if (!isset($this->stories[$name])) {
79-
$groupsNames = array_keys($this->groupedStories);
78+
$groupsNames = \array_keys($this->groupedStories);
8079
$name = $io->choice('Choose a group of stories:', $groupsNames);
8180
}
8281
}
@@ -92,14 +91,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9291
}
9392

9493
if (!$stories) {
95-
throw new InvalidArgumentException("Story with name \"$name\" does not exist.");
94+
throw new InvalidArgumentException("Story with name \"{$name}\" does not exist.");
9695
}
9796

9897
foreach ($stories as $name => $storyClass) {
9998
$storyClass::load();
10099

101100
if ($io->isVerbose()) {
102-
$io->info("Story \"$storyClass\" loaded (name: $name).");
101+
$io->info("Story \"{$storyClass}\" loaded (name: {$name}).");
103102
}
104103
}
105104

@@ -110,6 +109,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
110109

111110
private function resetDatabase(): void
112111
{
112+
// it is very not likely that we need dama when running this command
113+
if (\class_exists(StaticDriver::class) && StaticDriver::isKeepStaticConnections()) {
114+
StaticDriver::setKeepStaticConnections(false);
115+
}
116+
113117
foreach ($this->databaseResetters as $databaseResetter) {
114118
$databaseResetter->resetBeforeFirstTest($this->kernel);
115119
}

src/DependencyInjection/AsFixtureStoryCompilerPass.php

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\DependencyInjection;
413

514
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -19,16 +28,14 @@ public function process(ContainerBuilder $container): void
1928
$fixtureStories = [];
2029
$groupedFixtureStories = [];
2130
foreach ($container->findTaggedServiceIds('foundry.story.fixture') as $id => $tags) {
22-
if (count($tags) !== 1) {
31+
if (1 !== \count($tags)) {
2332
throw new LogicException('Tag "foundry.story.fixture" must be used only once per service.');
2433
}
2534

2635
$name = $tags[0]['name'];
2736

2837
if (isset($fixtureStories[$name])) {
29-
throw new LogicException(
30-
"Cannot use #[AsFixture] name \"{$name}\" for service \"{$id}\". This name is already used by service \"{$fixtureStories[$name]}\"."
31-
);
38+
throw new LogicException("Cannot use #[AsFixture] name \"{$name}\" for service \"{$id}\". This name is already used by service \"{$fixtureStories[$name]}\".");
3239
}
3340

3441
$storyClass = $container->findDefinition($id)->getClass();
@@ -46,11 +53,9 @@ public function process(ContainerBuilder $container): void
4653
}
4754
}
4855

49-
if ($collisionNames = array_intersect(array_keys($fixtureStories), array_keys($groupedFixtureStories))) {
50-
$collisionNames = implode('", "', $collisionNames);
51-
throw new LogicException(
52-
"Cannot use #[AsFixture] group(s) \"{$collisionNames}\", they collide with fixture names."
53-
);
56+
if ($collisionNames = \array_intersect(\array_keys($fixtureStories), \array_keys($groupedFixtureStories))) {
57+
$collisionNames = \implode('", "', $collisionNames);
58+
throw new LogicException("Cannot use #[AsFixture] group(s) \"{$collisionNames}\", they collide with fixture names.");
5459
}
5560

5661
$container->findDefinition('.zenstruck_foundry.story.load_story-command')

src/ZenstruckFoundryBundle.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
1515
use Symfony\Component\DependencyInjection\ChildDefinition;
1616
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17-
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
1817
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Exception\LogicException;
1919
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
2020
use Symfony\Component\DependencyInjection\Reference;
2121
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
@@ -29,7 +29,6 @@
2929
use Zenstruck\Foundry\ORM\ResetDatabase\OrmResetter;
3030
use Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode;
3131
use Zenstruck\Foundry\ORM\ResetDatabase\SchemaDatabaseResetter;
32-
use Symfony\Component\DependencyInjection\Exception\LogicException;
3332

3433
/**
3534
* @author Kevin Bond <[email protected]>
@@ -437,10 +436,8 @@ private function configureFixturesStory(ContainerConfigurator $configurator, Con
437436
AsFixture::class,
438437
// @phpstan-ignore argument.type
439438
static function(ChildDefinition $definition, AsFixture $attribute, \ReflectionClass $reflector) {
440-
if (false === $reflector->getParentClass() || $reflector->getParentClass()->getName() !== Story::class) {
441-
throw new LogicException(
442-
\sprintf("Only stories can be marked with \"%s\" attribute, class \"%s\" is not a story.", AsFixture::class, $reflector->getName())
443-
);
439+
if (false === $reflector->getParentClass() || Story::class !== $reflector->getParentClass()->getName()) {
440+
throw new LogicException(\sprintf('Only stories can be marked with "%s" attribute, class "%s" is not a story.', AsFixture::class, $reflector->getName()));
444441
}
445442

446443
$definition->addTag('foundry.story.fixture', ['name' => $attribute->name, 'groups' => $attribute->groups]);

tests/Fixture/Stories/Fixtures/FixtureInGroupStory.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures;
413

514
use Zenstruck\Foundry\Attribute\AsFixture;

tests/Fixture/Stories/Fixtures/FixtureStory.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures;
413

514
use Zenstruck\Foundry\Attribute\AsFixture;

tests/Fixture/Stories/Fixtures/FixtureStoryWithGroupNameCollision.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures;
413

514
use Zenstruck\Foundry\Attribute\AsFixture;

tests/Fixture/Stories/Fixtures/FixtureStoryWithNameCollision.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures;
413

514
use Zenstruck\Foundry\Attribute\AsFixture;

tests/Fixture/Stories/Fixtures/FixtureUsingAnotherFixtureStory.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures;
413

514
use Zenstruck\Foundry\Attribute\AsFixture;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
6+
Zenstruck\Foundry\Tests\Fixture\Stories\Fixtures\FixtureStory: ~

tests/Integration/Command/LoadStoryTest.php renamed to tests/Integration/Command/LoadStoryCommandTest.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
<?php
22

3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
312
namespace Zenstruck\Foundry\Tests\Integration\Command;
413

514
use PHPUnit\Framework\Attributes\DataProvider;
615
use PHPUnit\Framework\Attributes\Test;
16+
use Symfony\Bundle\FrameworkBundle\Console\Application;
17+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
718
use Symfony\Component\Console\Exception\InvalidArgumentException;
819
use Symfony\Component\Console\Exception\LogicException;
920
use Symfony\Component\Console\Output\ConsoleOutput;
1021
use Symfony\Component\Console\Tester\CommandTester;
11-
use Symfony\Bundle\FrameworkBundle\Console\Application;
12-
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1322
use Zenstruck\Foundry\Test\Factories;
1423
use Zenstruck\Foundry\Test\ResetDatabase;
1524
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;
@@ -18,9 +27,9 @@
1827
use Zenstruck\Foundry\Tests\Fixture\TestKernel;
1928
use Zenstruck\Foundry\Tests\Integration\RequiresORM;
2029

21-
final class LoadStoryTest extends KernelTestCase
30+
final class LoadStoryCommandTest extends KernelTestCase
2231
{
23-
use RequiresORM, ResetDatabase, Factories;
32+
use Factories, RequiresORM, ResetDatabase;
2433

2534
/**
2635
* @test
@@ -81,7 +90,7 @@ public function it_throws_if_name_collision_between_two_stories_name(): void
8190
{
8291
$this->expectException(\LogicException::class);
8392
$this->expectExceptionMessage(
84-
sprintf(
93+
\sprintf(
8594
'Cannot use #[AsFixture] name "fixture-story" for service "%s". This name is already used by service "%s".',
8695
FixtureStoryWithNameCollision::class,
8796
FixtureStory::class,
@@ -145,8 +154,8 @@ public function it_can_load_fixture_which_loads_another_fixture(string $name): v
145154

146155
public static function provideFixturesWhichLoadAnotherFixtureCases(): iterable
147156
{
148-
yield 'by fixture name' => ['fixture-using-another-fixture'];
149-
yield 'by group name' => ['fixture-using-another-fixture-group'];
157+
yield 'by fixture name' => ['fixture-using-another-fixture'];
158+
yield 'by group name' => ['fixture-using-another-fixture-group'];
150159
}
151160

152161
/**
@@ -219,6 +228,21 @@ public function if_no_name_provided_it_asks_for_group_to_load(): void
219228
self::assertStringContainsString('Loading stories group "multiple-fixtures-in-group"', $commandTester->getDisplay());
220229
}
221230

231+
/**
232+
* @test
233+
*/
234+
#[Test]
235+
public function if_no_name_provided_and_on_one_story_fixture_it_loads_it_automatically(): void
236+
{
237+
$commandTester = $this->commandTester(['environment' => 'stories_as_fixture_unique']);
238+
$commandTester->execute(['--append' => true]);
239+
240+
GenericEntityFactory::assert()->count(1);
241+
GenericEntityFactory::assert()->count(1, ['prop1' => 'fixture-story']);
242+
243+
self::assertStringContainsString('Loading story with name "fixture-story"', $commandTester->getDisplay());
244+
}
245+
222246
private function commandTester(array $options = []): CommandTester
223247
{
224248
return new CommandTester((new Application(self::bootKernel($options)))->find('foundry:load-stories'));

0 commit comments

Comments
 (0)