From 391844692f3bcdb2d9c60fcb9256e488e3707edb Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Mon, 2 Dec 2024 11:01:09 +0100 Subject: [PATCH] feat: allow WithStory attribute on parent class --- .../BootFoundryOnDataProviderMethodCalled.php | 2 +- src/PHPUnit/BuildStoryOnTestPrepared.php | 33 +++++++++++++++-- ...ownFoundryOnDataProviderMethodFinished.php | 2 +- tests/Fixture/Stories/ServiceStory.php | 35 +++++++++++++++++++ tests/Fixture/TestKernel.php | 2 ++ .../ParentClassWithStoryAttributeTestCase.php | 20 +++++++++++ .../WithStory/WithStoryOnClassTest.php | 11 +++++- .../WithStory/WithStoryOnMethodTest.php | 21 ++++++++++- .../WithStory/WithStoryOnParentClassTest.php | 30 ++++++++++++++++ 9 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 tests/Fixture/Stories/ServiceStory.php create mode 100644 tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php create mode 100644 tests/Integration/Attribute/WithStory/WithStoryOnParentClassTest.php diff --git a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php index 2eccf412..4564efb6 100644 --- a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php +++ b/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php @@ -24,7 +24,7 @@ final class BootFoundryOnDataProviderMethodCalled implements Event\Test\DataProv public function notify(Event\Test\DataProviderMethodCalled $event): void { if (\method_exists($event->testMethod()->className(), '_bootForDataProvider')) { - \call_user_func([$event->testMethod()->className(), '_bootForDataProvider']); + $event->testMethod()->className()::_bootForDataProvider(); } } } diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php index a1dcdc1b..c93dd8bf 100644 --- a/src/PHPUnit/BuildStoryOnTestPrepared.php +++ b/src/PHPUnit/BuildStoryOnTestPrepared.php @@ -14,7 +14,9 @@ namespace Zenstruck\Foundry\PHPUnit; use PHPUnit\Event; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; +use Zenstruck\Foundry\Exception\CannotUseServiceStory; /** * @internal @@ -32,17 +34,44 @@ public function notify(Event\Test\Prepared $event): void /** @var Event\Code\TestMethod $test */ + $reflectionClass = new \ReflectionClass($test->className()); $withStoryAttributes = [ - ...(new \ReflectionClass($test->className()))->getAttributes(WithStory::class), - ...(new \ReflectionMethod($test->className(), $test->methodName()))->getAttributes(WithStory::class), + ...$this->collectWithStoryAttributesFromClassAndParents($reflectionClass), + ...$reflectionClass->getMethod($test->methodName())->getAttributes(WithStory::class), ]; if (!$withStoryAttributes) { return; } + if (!is_subclass_of($test->className(), KernelTestCase::class)) { + throw new \InvalidArgumentException( + \sprintf( + 'The test class "%s" must extend "%s" to use the "%s" attribute.', + $test->className(), + KernelTestCase::class, + WithStory::class + ) + ); + } + foreach ($withStoryAttributes as $withStoryAttribute) { $withStoryAttribute->newInstance()->story::load(); } } + + /** + * @return list<\ReflectionAttribute> + */ + private function collectWithStoryAttributesFromClassAndParents(\ReflectionClass $class): array // @phpstan-ignore missingType.generics + { + return [ + ...$class->getAttributes(WithStory::class), + ...( + $class->getParentClass() + ? $this->collectWithStoryAttributesFromClassAndParents($class->getParentClass()) + : [] + ) + ]; + } } diff --git a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php b/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php index 6fbe1394..b028394b 100644 --- a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php +++ b/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php @@ -24,7 +24,7 @@ final class ShutdownFoundryOnDataProviderMethodFinished implements Event\Test\Da public function notify(Event\Test\DataProviderMethodFinished $event): void { if (\method_exists($event->testMethod()->className(), '_shutdownAfterDataProvider')) { - \call_user_func([$event->testMethod()->className(), '_shutdownAfterDataProvider']); + $event->testMethod()->className()::_shutdownAfterDataProvider(); } } } diff --git a/tests/Fixture/Stories/ServiceStory.php b/tests/Fixture/Stories/ServiceStory.php new file mode 100644 index 00000000..8543dd60 --- /dev/null +++ b/tests/Fixture/Stories/ServiceStory.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Fixture\Stories; + +use Symfony\Component\HttpKernel\KernelInterface; +use Zenstruck\Foundry\Story; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; + +/** + * @author Nicolas PHILIPPE + */ + final class ServiceStory extends Story +{ + public function __construct( + private readonly KernelInterface $kernel + ) { + } + + public function build(): void + { + $this->addState( + 'foo', + GenericEntityFactory::createOne(['prop1' => $this->kernel->getEnvironment()]) + ); + } +} diff --git a/tests/Fixture/TestKernel.php b/tests/Fixture/TestKernel.php index 63e61850..589e903e 100644 --- a/tests/Fixture/TestKernel.php +++ b/tests/Fixture/TestKernel.php @@ -28,6 +28,7 @@ use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalInvokableService; use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalStory; +use Zenstruck\Foundry\Tests\Fixture\Stories\ServiceStory; use Zenstruck\Foundry\ZenstruckFoundryBundle; /** @@ -162,6 +163,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $c->register(GlobalInvokableService::class); $c->register(ArrayFactory::class)->setAutowired(true)->setAutoconfigured(true); $c->register(Object1Factory::class)->setAutowired(true)->setAutoconfigured(true); + $c->register(ServiceStory::class)->setAutowired(true)->setAutoconfigured(true); } protected function configureRoutes(RoutingConfigurator $routes): void diff --git a/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php b/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php new file mode 100644 index 00000000..4e93bf6c --- /dev/null +++ b/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php @@ -0,0 +1,20 @@ + + */ +#[WithStory(EntityStory::class)] +abstract class ParentClassWithStoryAttributeTestCase extends KernelTestCase +{ + use Factories, ResetDatabase; +} diff --git a/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php b/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php index 72ae1725..08b5150f 100644 --- a/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php +++ b/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php @@ -2,16 +2,25 @@ declare(strict_types=1); -namespace Integration\Attribute\WithStory; +namespace Zenstruck\Foundry\Tests\Integration\Attribute\WithStory; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; use Zenstruck\Foundry\Tests\Fixture\Stories\EntityPoolStory; use Zenstruck\Foundry\Tests\Fixture\Stories\EntityStory; +/** + * @author Nicolas PHILIPPE + * @requires PHPUnit 11.4 + */ +#[RequiresPhpunit('11.4')] +#[RequiresPhpunitExtension(FoundryExtension::class)] #[WithStory(EntityStory::class)] final class WithStoryOnClassTest extends KernelTestCase { diff --git a/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php b/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php index 9c0e5731..3ed6c6e2 100644 --- a/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php +++ b/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php @@ -2,16 +2,26 @@ declare(strict_types=1); -namespace Integration\Attribute\WithStory; +namespace Zenstruck\Foundry\Tests\Integration\Attribute\WithStory; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; use Zenstruck\Foundry\Tests\Fixture\Stories\EntityPoolStory; use Zenstruck\Foundry\Tests\Fixture\Stories\EntityStory; +use Zenstruck\Foundry\Tests\Fixture\Stories\ServiceStory; +/** + * @author Nicolas PHILIPPE + * @requires PHPUnit 11.4 + */ +#[RequiresPhpunit('11.4')] +#[RequiresPhpunitExtension(FoundryExtension::class)] final class WithStoryOnMethodTest extends KernelTestCase { use Factories, ResetDatabase; @@ -37,4 +47,13 @@ public function can_use_multiple_story_in_attribute(): void { GenericEntityFactory::assert()->count(5); } + + /** + * @test + */ + #[WithStory(ServiceStory::class)] + public function can_use_service_story(): void + { + $this->assertSame('dev', ServiceStory::get('foo')->getProp1()); + } } diff --git a/tests/Integration/Attribute/WithStory/WithStoryOnParentClassTest.php b/tests/Integration/Attribute/WithStory/WithStoryOnParentClassTest.php new file mode 100644 index 00000000..aa865752 --- /dev/null +++ b/tests/Integration/Attribute/WithStory/WithStoryOnParentClassTest.php @@ -0,0 +1,30 @@ + + * @requires PHPUnit 11.4 + */ +#[RequiresPhpunit('11.4')] +#[RequiresPhpunitExtension(FoundryExtension::class)] +#[WithStory(EntityPoolStory::class)] +final class WithStoryOnParentClassTest extends ParentClassWithStoryAttributeTestCase +{ + /** + * @test + */ + public function can_use_story_in_attribute_from_parent_class(): void + { + GenericEntityFactory::assert()->count(5); + } +}