From be64c7e16bdaacefa0bc35e5c8aa8022dd8abd94 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Mon, 25 Aug 2025 19:35:54 +0200 Subject: [PATCH 1/7] feat: force PHPUnit extension usage --- phpunit-deprecation-baseline.xml | 3 +- src/Configuration.php | 6 ++ src/Exception/FoundryNotBooted.php | 8 ++- .../BootFoundryOnDataProviderMethodCalled.php | 33 ++++++++- .../BootFoundryOnPreparationStarted.php | 62 ++++++++++++++++ src/PHPUnit/FoundryExtension.php | 72 +++++++++++++------ src/PHPUnit/KernelTestCaseHelper.php | 45 ++++++++++++ ...ownFoundryOnDataProviderMethodFinished.php | 7 +- src/PHPUnit/ShutdownFoundryOnTestFinished.php | 29 ++++++++ src/Test/Factories.php | 51 ++++--------- src/Test/ResetDatabase.php | 2 +- ...DatabaseTraitTestWithoutFactoriesTest.php} | 2 +- ...php => KernelTestWithoutFactoriesTest.php} | 2 +- 13 files changed, 253 insertions(+), 69 deletions(-) create mode 100644 src/PHPUnit/BootFoundryOnPreparationStarted.php create mode 100644 src/PHPUnit/KernelTestCaseHelper.php create mode 100644 src/PHPUnit/ShutdownFoundryOnTestFinished.php rename tests/Integration/ForceFactoriesTraitUsage/{KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php => KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php} (94%) rename tests/Integration/ForceFactoriesTraitUsage/{KernelTestWithoutFactoriesTrait.php => KernelTestWithoutFactoriesTest.php} (88%) diff --git a/phpunit-deprecation-baseline.xml b/phpunit-deprecation-baseline.xml index 4b2706232..b0cbd1995 100644 --- a/phpunit-deprecation-baseline.xml +++ b/phpunit-deprecation-baseline.xml @@ -12,8 +12,9 @@ + + - diff --git a/src/Configuration.php b/src/Configuration.php index b21b6836a..1274ac689 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -21,6 +21,8 @@ use Zenstruck\Foundry\InMemory\InMemoryRepositoryRegistry; use Zenstruck\Foundry\Persistence\PersistedObjectsTracker; use Zenstruck\Foundry\Persistence\PersistenceManager; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Test\Factories; /** * @author Kevin Bond @@ -143,6 +145,10 @@ public static function isBooted(): bool public static function boot(\Closure|self $configuration): void { self::$instance = $configuration; + + if (FoundryExtension::shouldBeEnabled()) { + trigger_deprecation('zenstruck/foundry', '2.7', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3.'); + } } /** @param \Closure():self|self $configuration */ diff --git a/src/Exception/FoundryNotBooted.php b/src/Exception/FoundryNotBooted.php index 5f7fbef27..d6fa3fc8c 100644 --- a/src/Exception/FoundryNotBooted.php +++ b/src/Exception/FoundryNotBooted.php @@ -11,6 +11,8 @@ namespace Zenstruck\Foundry\Exception; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; + /** * @author Kevin Bond */ @@ -18,6 +20,10 @@ final class FoundryNotBooted extends \LogicException { public function __construct() { - parent::__construct('Foundry is not yet booted. Ensure ZenstruckFoundryBundle is enabled. If in a test, ensure your TestCase has the Factories trait.'); + $message = FoundryExtension::isEnabled() + ? 'Foundry is not yet booted. Ensure ZenstruckFoundryBundle is enabled. If in a test, ensure Foundry\'s PHPUnit extension is enabled.' + : 'Foundry is not yet booted. Ensure ZenstruckFoundryBundle is enabled. If in a test, ensure your TestCase has the Factories trait.'; + + parent::__construct($message); } } diff --git a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php index 8f58ca6bb..e224c55d1 100644 --- a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php +++ b/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php @@ -14,8 +14,11 @@ namespace Zenstruck\Foundry\PHPUnit; use PHPUnit\Event; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\InMemory\AsInMemoryTest; +use Zenstruck\Foundry\Test\UnitTestConfig; /** * @internal @@ -25,9 +28,7 @@ final class BootFoundryOnDataProviderMethodCalled implements Event\Test\DataProv { public function notify(Event\Test\DataProviderMethodCalled $event): void { - if (\method_exists($event->testMethod()->className(), '_bootForDataProvider')) { - $event->testMethod()->className()::_bootForDataProvider(); - } + $this->bootFoundryForDataProvider($event->testMethod()->className()); $testMethod = $event->testMethod(); @@ -35,4 +36,30 @@ public function notify(Event\Test\DataProviderMethodCalled $event): void Configuration::instance()->enableInMemory(); } } + + /** + * @param class-string $className + */ + private function bootFoundryForDataProvider(string $className): void + { + if (!\is_subclass_of($className, TestCase::class)) { + return; + } + + // unit test + if (!\is_subclass_of($className, KernelTestCase::class)) { + Configuration::bootForDataProvider(UnitTestConfig::build()); + + return; + } + + // integration test + Configuration::bootForDataProvider(static function() use ($className): Configuration { + if (!KernelTestCaseHelper::getContainerForTestClass($className)->has('.zenstruck_foundry.configuration')) { + throw new \LogicException('ZenstruckFoundryBundle is not enabled. Ensure it is added to your config/bundles.php.'); + } + + return KernelTestCaseHelper::getContainerForTestClass($className)->get('.zenstruck_foundry.configuration'); // @phpstan-ignore return.type + }); + } } diff --git a/src/PHPUnit/BootFoundryOnPreparationStarted.php b/src/PHPUnit/BootFoundryOnPreparationStarted.php new file mode 100644 index 000000000..a7a773f0e --- /dev/null +++ b/src/PHPUnit/BootFoundryOnPreparationStarted.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\PHPUnit; + +use PHPUnit\Event; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\Test\UnitTestConfig; + +/** + * @internal + * @author Nicolas PHILIPPE + */ +final class BootFoundryOnPreparationStarted implements Event\Test\PreparationStartedSubscriber +{ + public function notify(Event\Test\PreparationStarted $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->bootFoundry($event->test()->className()); + } + + /** + * @param class-string $className + */ + private function bootFoundry(string $className): void + { + if (!\is_subclass_of($className, TestCase::class)) { + return; + } + + // unit test + if (!\is_subclass_of($className, KernelTestCase::class)) { + Configuration::boot(UnitTestConfig::build()); + + return; + } + + // integration test + Configuration::boot(static function() use ($className): Configuration { + if (!KernelTestCaseHelper::getContainerForTestClass($className)->has('.zenstruck_foundry.configuration')) { + throw new \LogicException('ZenstruckFoundryBundle is not enabled. Ensure it is added to your config/bundles.php.'); + } + + return KernelTestCaseHelper::getContainerForTestClass($className)->get('.zenstruck_foundry.configuration'); // @phpstan-ignore return.type + }); + } +} diff --git a/src/PHPUnit/FoundryExtension.php b/src/PHPUnit/FoundryExtension.php index 240e8c4be..fbf762e0c 100644 --- a/src/PHPUnit/FoundryExtension.php +++ b/src/PHPUnit/FoundryExtension.php @@ -22,30 +22,62 @@ * @internal * @author Nicolas PHILIPPE */ -final class FoundryExtension implements Runner\Extension\Extension -{ - public function bootstrap( - TextUI\Configuration\Configuration $configuration, - Runner\Extension\Facade $facade, - Runner\Extension\ParameterCollection $parameters, - ): void { - // shutdown Foundry if for some reason it has been booted before - if (Configuration::isBooted()) { - Configuration::shutdown(); + +if (interface_exists(Runner\Extension\Extension::class)) { + final class FoundryExtension implements Runner\Extension\Extension + { + private static bool $enabled = false; + + public function bootstrap( + TextUI\Configuration\Configuration $configuration, + Runner\Extension\Facade $facade, + Runner\Extension\ParameterCollection $parameters, + ): void { + // shutdown Foundry if for some reason it has been booted before + if (Configuration::isBooted()) { + Configuration::shutdown(); + } + + $subscribers = [ + new BuildStoryOnTestPrepared(), + new EnableInMemoryBeforeTest(), + new DisplayFakerSeedOnTestSuiteFinished(), + new BootFoundryOnPreparationStarted(), + new ShutdownFoundryOnTestFinished(), + ]; + + if (ConstraintRequirement::from('>=11.4')->isSatisfiedBy(Runner\Version::id())) { + // those deal with data provider events which can be useful only if PHPUnit >=11.4 is used + $subscribers[] = new BootFoundryOnDataProviderMethodCalled(); + $subscribers[] = new ShutdownFoundryOnDataProviderMethodFinished(); + } + + $facade->registerSubscribers(...$subscribers); + + self::$enabled = true; } - $subscribers = [ - new BuildStoryOnTestPrepared(), - new EnableInMemoryBeforeTest(), - new DisplayFakerSeedOnTestSuiteFinished(), - ]; + public static function shouldBeEnabled(): bool + { + return !self::isEnabled() && ConstraintRequirement::from('>=10')->isSatisfiedBy(Runner\Version::id()); + } - if (ConstraintRequirement::from('>=11.4')->isSatisfiedBy(Runner\Version::id())) { - // those deal with data provider events which can be useful only if PHPUnit >=11.4 is used - $subscribers[] = new BootFoundryOnDataProviderMethodCalled(); - $subscribers[] = new ShutdownFoundryOnDataProviderMethodFinished(); + public static function isEnabled(): bool + { + return self::$enabled; + } + } +} else { + final class FoundryExtension + { + public static function shouldBeEnabled(): bool + { + return false; } - $facade->registerSubscribers(...$subscribers); + public static function isEnabled(): bool + { + return false; + } } } diff --git a/src/PHPUnit/KernelTestCaseHelper.php b/src/PHPUnit/KernelTestCaseHelper.php new file mode 100644 index 000000000..12f7ff4ca --- /dev/null +++ b/src/PHPUnit/KernelTestCaseHelper.php @@ -0,0 +1,45 @@ + $class::getContainer(), + newThis: null, + newScope: $class, + ))(); + } + + /** + * @param class-string $class + */ + public static function tearDownClass(string $class): void + { + if (!\is_subclass_of($class, TestCase::class)) { + throw new \LogicException(\sprintf('Class "%s" must extend "%s".', $class, TestCase::class)); + } + + (\Closure::bind( + fn() => $class::tearDownAfterClass(), + newThis: null, + newScope: $class, + ))(); + } +} diff --git a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php b/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php index b028394b3..ed5f29f9e 100644 --- a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php +++ b/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php @@ -14,6 +14,7 @@ namespace Zenstruck\Foundry\PHPUnit; use PHPUnit\Event; +use Zenstruck\Foundry\Configuration; /** * @internal @@ -23,8 +24,8 @@ final class ShutdownFoundryOnDataProviderMethodFinished implements Event\Test\Da { public function notify(Event\Test\DataProviderMethodFinished $event): void { - if (\method_exists($event->testMethod()->className(), '_shutdownAfterDataProvider')) { - $event->testMethod()->className()::_shutdownAfterDataProvider(); - } + KernelTestCaseHelper::tearDownClass($event->testMethod()->className()); + + Configuration::shutdown(); } } diff --git a/src/PHPUnit/ShutdownFoundryOnTestFinished.php b/src/PHPUnit/ShutdownFoundryOnTestFinished.php new file mode 100644 index 000000000..bfa9b99fb --- /dev/null +++ b/src/PHPUnit/ShutdownFoundryOnTestFinished.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\PHPUnit; + +use PHPUnit\Event; +use Zenstruck\Foundry\Configuration; + +/** + * @internal + * @author Nicolas PHILIPPE + */ +final class ShutdownFoundryOnTestFinished implements Event\Test\FinishedSubscriber +{ + public function notify(Event\Test\Finished $event): void + { + Configuration::shutdown(); + } +} diff --git a/src/Test/Factories.php b/src/Test/Factories.php index 8bd39cf89..d04aae29f 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -16,6 +16,8 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; + use function Zenstruck\Foundry\Persistence\initialize_proxy_object; /** @@ -27,58 +29,31 @@ trait Factories * @internal * @before */ - #[Before] + #[Before(5)] public function _beforeHook(): void { - $this->_bootFoundry(); - $this->_loadDataProvidedProxies(); - } + $this->_loadDataProvidedProxies(); // todo remove - /** - * @internal - * @after - */ - #[After] - public static function _shutdownFoundry(): void - { - Configuration::shutdown(); - } - - /** - * @see \Zenstruck\Foundry\PHPUnit\BootFoundryOnDataProviderMethodCalled - * @internal - */ - public static function _bootForDataProvider(): void - { - if (!\is_subclass_of(static::class, KernelTestCase::class)) { // @phpstan-ignore function.impossibleType, function.alreadyNarrowedType - // unit test - Configuration::bootForDataProvider(UnitTestConfig::build()); + if (FoundryExtension::isEnabled()) { + trigger_deprecation('zenstruck/foundry', '2.7', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); return; } - // integration test - Configuration::bootForDataProvider(static function(): Configuration { - if (!static::getContainer()->has('.zenstruck_foundry.configuration')) { // @phpstan-ignore staticMethod.notFound - throw new \LogicException('ZenstruckFoundryBundle is not enabled. Ensure it is added to your config/bundles.php.'); - } - - return static::getContainer()->get('.zenstruck_foundry.configuration'); // @phpstan-ignore staticMethod.notFound, return.type - }); + $this->_bootFoundry(); } /** * @internal - * @see \Zenstruck\Foundry\PHPUnit\ShutdownFoundryOnDataProviderMethodFinished + * @after */ - public static function _shutdownAfterDataProvider(): void + #[After(5)] + public static function _shutdownFoundry(): void { - if (\is_subclass_of(static::class, KernelTestCase::class)) { // @phpstan-ignore function.impossibleType, function.alreadyNarrowedType - self::ensureKernelShutdown(); // @phpstan-ignore staticMethod.notFound - static::$class = null; // @phpstan-ignore staticProperty.notFound - static::$kernel = null; // @phpstan-ignore staticProperty.notFound - static::$booted = false; // @phpstan-ignore staticProperty.notFound + if (FoundryExtension::isEnabled()) { + return; } + Configuration::shutdown(); } diff --git a/src/Test/ResetDatabase.php b/src/Test/ResetDatabase.php index d17e8bf39..f07cf1e1b 100644 --- a/src/Test/ResetDatabase.php +++ b/src/Test/ResetDatabase.php @@ -42,7 +42,7 @@ public static function _resetDatabaseBeforeFirstTest(): void * @internal * @before */ - #[Before] + #[Before(10)] public static function _resetDatabaseBeforeEachTest(): void { if (!\is_subclass_of(static::class, KernelTestCase::class)) { // @phpstan-ignore function.alreadyNarrowedType diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php similarity index 94% rename from tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php rename to tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php index 889784d62..8d36fa425 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php @@ -18,7 +18,7 @@ use Zenstruck\Foundry\Test\ResetDatabase; #[RequiresPhpunit('>=11.0')] -final class KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait extends KernelTestCase +final class KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest extends KernelTestCase { use KernelTestCaseWithoutFactoriesTrait; use ResetDatabase; diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTrait.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php similarity index 88% rename from tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTrait.php rename to tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php index 38669bd1c..87e56c8fe 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTrait.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php @@ -17,7 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; #[RequiresPhpunit('>=11.0')] -final class KernelTestWithoutFactoriesTrait extends KernelTestCase +final class KernelTestWithoutFactoriesTest extends KernelTestCase { use KernelTestCaseWithoutFactoriesTrait; } From 67e88a0a50024a8a6bcaebdc5bbac85658fc8e32 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Tue, 26 Aug 2025 09:03:13 +0200 Subject: [PATCH 2/7] feat: don't force using Factories trait when PHPUnit extension is enabled --- .github/workflows/phpunit.yml | 2 +- src/Configuration.php | 4 +- src/Test/Factories.php | 10 ++--- .../AnotherBaseTestCase.php | 2 + ...TestCaseWithBothTraitsInWrongOrderTest.php | 2 +- .../KernelTestCaseWithBothTraitsTest.php | 2 +- .../KernelTestCaseWithFactoriesTraitTest.php | 2 +- ...tDatabaseTraitTestWithoutFactoriesTest.php | 1 + .../KernelTestWithoutFactoriesTest.php | 2 +- .../SkipWithPHPUnitExtension.php | 21 +++++++++ .../UnitTestCaseWithFactoriesTraitTest.php | 2 +- .../UnitTestCaseWithoutFactoriesTraitTest.php | 2 + ...ernelTestCaseWithoutFactoriesTraitTest.php | 44 +++++++++++++++++++ .../UnitTestCaseWithoutFactoriesTraitTest.php | 34 ++++++++++++++ 14 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/UsingExtension/KernelTestCaseWithoutFactoriesTraitTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/UsingExtension/UnitTestCaseWithoutFactoriesTraitTest.php diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 5ba91e373..9c724cb41 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -202,7 +202,7 @@ jobs: strategy: fail-fast: false matrix: - php: [ 8.1, 8.2, 8.3, 8.4 ] + php: [ 8.2, 8.3, 8.4 ] steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/src/Configuration.php b/src/Configuration.php index 1274ac689..2f48df667 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -131,7 +131,9 @@ public static function instance(): self throw new FoundryNotBooted(); } - FactoriesTraitNotUsed::throwIfComingFromKernelTestCaseWithoutFactoriesTrait(); + if (!FoundryExtension::isEnabled()) { + FactoriesTraitNotUsed::throwIfComingFromKernelTestCaseWithoutFactoriesTrait(); + } return \is_callable(self::$instance) ? (self::$instance)() : self::$instance; } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index d04aae29f..2d325b181 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -32,15 +32,15 @@ trait Factories #[Before(5)] public function _beforeHook(): void { - $this->_loadDataProvidedProxies(); // todo remove - - if (FoundryExtension::isEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.7', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); + if (!FoundryExtension::isEnabled()) { + $this->_bootFoundry(); return; } - $this->_bootFoundry(); + trigger_deprecation('zenstruck/foundry', '2.7', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); + + $this->_loadDataProvidedProxies(); // todo remove } /** diff --git a/tests/Integration/ForceFactoriesTraitUsage/AnotherBaseTestCase.php b/tests/Integration/ForceFactoriesTraitUsage/AnotherBaseTestCase.php index c77c72559..1a7f3fec6 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/AnotherBaseTestCase.php +++ b/tests/Integration/ForceFactoriesTraitUsage/AnotherBaseTestCase.php @@ -18,6 +18,8 @@ abstract class AnotherBaseTestCase extends KernelTestCase { + use SkipWithPHPUnitExtension; + // @phpstan-ignore missingType.generics public function useProxyClass(Proxy $proxy): void { diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php index 23e72634a..a2461a5ff 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php @@ -23,7 +23,7 @@ #[RequiresPhpunit('>=11.0')] final class KernelTestCaseWithBothTraitsInWrongOrderTest extends KernelTestCase { - use Factories, ResetDatabase; + use Factories, ResetDatabase, SkipWithPHPUnitExtension; #[Test] public function should_not_throw(): void diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php index fd6de3f93..4d84f3e2d 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php @@ -23,7 +23,7 @@ #[RequiresPhpunit('>=11.0')] final class KernelTestCaseWithBothTraitsTest extends KernelTestCase { - use Factories, ResetDatabase; + use Factories, ResetDatabase, SkipWithPHPUnitExtension; #[Test] public function should_not_throw(): void diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php index 80d40751d..fdb7cdf4e 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php @@ -22,7 +22,7 @@ #[RequiresPhpunit('>=11.0')] final class KernelTestCaseWithFactoriesTraitTest extends KernelTestCase { - use Factories; + use Factories, SkipWithPHPUnitExtension; #[Test] public function should_not_throw(): void diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php index 8d36fa425..6681111fd 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php @@ -22,4 +22,5 @@ final class KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest ext { use KernelTestCaseWithoutFactoriesTrait; use ResetDatabase; + use SkipWithPHPUnitExtension; } diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php index 87e56c8fe..ca9bf444d 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php @@ -19,5 +19,5 @@ #[RequiresPhpunit('>=11.0')] final class KernelTestWithoutFactoriesTest extends KernelTestCase { - use KernelTestCaseWithoutFactoriesTrait; + use KernelTestCaseWithoutFactoriesTrait, SkipWithPHPUnitExtension; } diff --git a/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php new file mode 100644 index 000000000..64ca8f8b9 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php @@ -0,0 +1,21 @@ +=11.0')] final class UnitTestCaseWithFactoriesTraitTest extends TestCase { - use Factories; + use Factories, SkipWithPHPUnitExtension; #[Test] public function should_not_throw(): void diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php index 2275b1af3..9a8e1c043 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php @@ -22,6 +22,8 @@ #[RequiresPhpunit('>=11.0')] final class UnitTestCaseWithoutFactoriesTraitTest extends TestCase { + use SkipWithPHPUnitExtension; + #[Test] public function should_throw(): void { diff --git a/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/KernelTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/KernelTestCaseWithoutFactoriesTraitTest.php new file mode 100644 index 000000000..9b9c43cfd --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/KernelTestCaseWithoutFactoriesTraitTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage\UsingExtension; + +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +#[RequiresPhpunitExtension(FoundryExtension::class)] +final class KernelTestCaseWithoutFactoriesTraitTest extends KernelTestCase +{ + #[Test] + public function should_not_throw(): void + { + Object1Factory::createOne(); + + $this->expectNotToPerformAssertions(); + } + + #[Test] + public function should_not_throw_even_when_kernel_is_booted(): void + { + self::getContainer()->get('.zenstruck_foundry.configuration'); + + Object1Factory::createOne(); + + $this->expectNotToPerformAssertions(); + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/UnitTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/UnitTestCaseWithoutFactoriesTraitTest.php new file mode 100644 index 000000000..26fa7d1ca --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/UsingExtension/UnitTestCaseWithoutFactoriesTraitTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage\UsingExtension; + +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +#[RequiresPhpunitExtension(FoundryExtension::class)] +final class UnitTestCaseWithoutFactoriesTraitTest extends TestCase +{ + #[Test] + public function should_not_throw(): void + { + $this->expectNotToPerformAssertions(); + + Object1Factory::createOne(); + } +} From 42dfe9b630af20c3eecc2afb9b375a4b4621e1ee Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Mon, 22 Sep 2025 11:56:46 +0200 Subject: [PATCH 3/7] feat: trigger persistence from data providers without Factories trait --- phpstan.neon | 4 - phpunit-deprecation-baseline.xml | 4 +- src/Configuration.php | 2 +- .../BootFoundryOnPreparationStarted.php | 7 +- src/PHPUnit/BuildStoryOnTestPrepared.php | 4 +- .../BootFoundryOnDataProviderMethodCalled.php | 10 +- ...ownFoundryOnDataProviderMethodFinished.php | 3 +- ...rDataProviderPersistenceOnTestPrepared.php | 44 ++++++++ src/PHPUnit/FoundryExtension.php | 4 + src/Persistence/PersistentObjectFactory.php | 2 +- ...rsistentObjectFromDataProviderRegistry.php | 102 ++++++++++++++++++ .../PersistentProxyObjectFactory.php | 2 +- src/Persistence/ProxyGenerator.php | 38 +++---- src/Test/Factories.php | 13 +-- ...rWithPersistentFactoryInKernelTestCase.php | 14 ++- ...est.php => GenericDocumentFactoryTest.php} | 2 +- ...yTest.php => GenericEntityFactoryTest.php} | 2 +- 17 files changed, 208 insertions(+), 49 deletions(-) rename src/PHPUnit/{ => DataProvider}/BootFoundryOnDataProviderMethodCalled.php (80%) rename src/PHPUnit/{ => DataProvider}/ShutdownFoundryOnDataProviderMethodFinished.php (87%) create mode 100644 src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php create mode 100644 src/Persistence/PersistentObjectFromDataProviderRegistry.php rename tests/Integration/DataProvider/{GenericDocumentProxyFactoryTest.php => GenericDocumentFactoryTest.php} (92%) rename tests/Integration/DataProvider/{GenericEntityProxyFactoryTest.php => GenericEntityFactoryTest.php} (92%) diff --git a/phpstan.neon b/phpstan.neon index 9ac64d6fa..124e3db45 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -30,10 +30,6 @@ parameters: - identifier: missingType.iterableValue path: tests/ - # We support both PHPUnit versions (this method changed in PHPUnit 10) - - identifier: function.impossibleType - path: src/Test/Factories.php - # PHPStan does not understand PHP version checks - message: '#Comparison operation "(<|>|<=|>=)" between int<80\d+, 80\d+> and 80\d+ is always (false|true).#' diff --git a/phpunit-deprecation-baseline.xml b/phpunit-deprecation-baseline.xml index b0cbd1995..7b8e3ff88 100644 --- a/phpunit-deprecation-baseline.xml +++ b/phpunit-deprecation-baseline.xml @@ -12,8 +12,8 @@ - - + + diff --git a/src/Configuration.php b/src/Configuration.php index 2f48df667..fbe340a1c 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -149,7 +149,7 @@ public static function boot(\Closure|self $configuration): void self::$instance = $configuration; if (FoundryExtension::shouldBeEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.7', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3.'); + trigger_deprecation('zenstruck/foundry', '2.8', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3.'); } } diff --git a/src/PHPUnit/BootFoundryOnPreparationStarted.php b/src/PHPUnit/BootFoundryOnPreparationStarted.php index a7a773f0e..56f03527a 100644 --- a/src/PHPUnit/BootFoundryOnPreparationStarted.php +++ b/src/PHPUnit/BootFoundryOnPreparationStarted.php @@ -27,11 +27,14 @@ final class BootFoundryOnPreparationStarted implements Event\Test\PreparationSta { public function notify(Event\Test\PreparationStarted $event): void { - if (!$event->test()->isTestMethod()) { + $test = $event->test(); + + if (!$test->isTestMethod()) { return; } + /** @var Event\Code\TestMethod $test */ - $this->bootFoundry($event->test()->className()); + $this->bootFoundry($test->className()); } /** diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php index ff3ea9eb4..4e42376da 100644 --- a/src/PHPUnit/BuildStoryOnTestPrepared.php +++ b/src/PHPUnit/BuildStoryOnTestPrepared.php @@ -47,7 +47,9 @@ public function notify(Event\Test\Prepared $event): void throw new \InvalidArgumentException(\sprintf('The test class "%s" must extend "%s" to use the "%s" attribute.', $test->className(), KernelTestCase::class, WithStory::class)); } - FactoriesTraitNotUsed::throwIfClassDoesNotHaveFactoriesTrait($test->className()); + if (!FoundryExtension::isEnabled()) { + FactoriesTraitNotUsed::throwIfClassDoesNotHaveFactoriesTrait($test->className()); + } foreach ($withStoryAttributes as $withStoryAttribute) { $withStoryAttribute->newInstance()->story::load(); diff --git a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php similarity index 80% rename from src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php rename to src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php index e224c55d1..f967a92c5 100644 --- a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php +++ b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php @@ -11,13 +11,15 @@ * file that was distributed with this source code. */ -namespace Zenstruck\Foundry\PHPUnit; +namespace Zenstruck\Foundry\PHPUnit\DataProvider; use PHPUnit\Event; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\InMemory\AsInMemoryTest; +use Zenstruck\Foundry\Persistence\PersistentObjectFromDataProviderRegistry; +use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; use Zenstruck\Foundry\Test\UnitTestConfig; /** @@ -30,6 +32,12 @@ public function notify(Event\Test\DataProviderMethodCalled $event): void { $this->bootFoundryForDataProvider($event->testMethod()->className()); + PersistentObjectFromDataProviderRegistry::instance()->addDataset( + $event->testMethod()->className(), + $event->testMethod()->methodName(), + "{$event->dataProviderMethod()->className()}::{$event->dataProviderMethod()->methodName()}"(...) // @phpstan-ignore callable.nonCallable + ); + $testMethod = $event->testMethod(); if (AsInMemoryTest::shouldEnableInMemory($testMethod->className(), $testMethod->methodName())) { diff --git a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php similarity index 87% rename from src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php rename to src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php index ed5f29f9e..690ecfa7f 100644 --- a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php +++ b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php @@ -11,10 +11,11 @@ * file that was distributed with this source code. */ -namespace Zenstruck\Foundry\PHPUnit; +namespace Zenstruck\Foundry\PHPUnit\DataProvider; use PHPUnit\Event; use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; /** * @internal diff --git a/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php new file mode 100644 index 000000000..50fe7ecfd --- /dev/null +++ b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\PHPUnit\DataProvider; + +use PHPUnit\Event; +use Zenstruck\Foundry\Persistence\PersistentObjectFromDataProviderRegistry; + +/** + * @internal + * @author Nicolas PHILIPPE + */ +final class TriggerDataProviderPersistenceOnTestPrepared implements Event\Test\PreparedSubscriber +{ + public function notify(Event\Test\Prepared $event): void + { + $test = $event->test(); + + if (!$test->isTestMethod()) { + return; + } + /** @var Event\Code\TestMethod $test */ + + if (!$test->testData()->hasDataFromDataProvider() || $test->metadata()->isDataProvider()->isEmpty()) { + return; + } + + PersistentObjectFromDataProviderRegistry::instance()->triggerPersistenceForDataset( + $test->className(), + $test->methodName(), + $test->testData()->dataFromDataProvider()->dataSetName(), + ); + } +} diff --git a/src/PHPUnit/FoundryExtension.php b/src/PHPUnit/FoundryExtension.php index fbf762e0c..ea18c5252 100644 --- a/src/PHPUnit/FoundryExtension.php +++ b/src/PHPUnit/FoundryExtension.php @@ -17,6 +17,9 @@ use PHPUnit\Runner; use PHPUnit\TextUI; use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\PHPUnit\DataProvider\BootFoundryOnDataProviderMethodCalled; +use Zenstruck\Foundry\PHPUnit\DataProvider\ShutdownFoundryOnDataProviderMethodFinished; +use Zenstruck\Foundry\PHPUnit\DataProvider\TriggerDataProviderPersistenceOnTestPrepared; /** * @internal @@ -50,6 +53,7 @@ public function bootstrap( // those deal with data provider events which can be useful only if PHPUnit >=11.4 is used $subscribers[] = new BootFoundryOnDataProviderMethodCalled(); $subscribers[] = new ShutdownFoundryOnDataProviderMethodFinished(); + $subscribers[] = new TriggerDataProviderPersistenceOnTestPrepared(); } $facade->registerSubscribers(...$subscribers); diff --git a/src/Persistence/PersistentObjectFactory.php b/src/Persistence/PersistentObjectFactory.php index 8a7b8d6c2..ea5ca2a39 100644 --- a/src/Persistence/PersistentObjectFactory.php +++ b/src/Persistence/PersistentObjectFactory.php @@ -241,7 +241,7 @@ public function create(callable|array $attributes = []): object && $this->isPersisting() && !$this instanceof PersistentProxyObjectFactory ) { - return ProxyGenerator::wrapFactoryNativeProxy($this, $attributes); + return PersistentObjectFromDataProviderRegistry::instance()->deferObjectCreation($this->with($attributes)); } $object = parent::create($attributes); diff --git a/src/Persistence/PersistentObjectFromDataProviderRegistry.php b/src/Persistence/PersistentObjectFromDataProviderRegistry.php new file mode 100644 index 000000000..32dea858c --- /dev/null +++ b/src/Persistence/PersistentObjectFromDataProviderRegistry.php @@ -0,0 +1,102 @@ +count(1); + * } + * + * public static function provide(): iterable + * { + * yield [MyEntityFactory::createOne()]; + * } + * ``` + * + * Sadly, this cannot be done directly a subscriber, since PHPUnit does not give access to the actual tests instances. + * + * This class is highly hacky! + * We collect all the "datasets" and we trigger the persistence for each one before the test is executed. + * This means that de data providers are called twice. + * To prevent the persisted object from being different from the one returned by the data provider, we use a "buffer" so + * that we can return the same object for each data provider call. + * + * @internal + */ +final class PersistentObjectFromDataProviderRegistry +{ + private static ?self $instance = null; + + /** @var array> */ + private array $datasets = []; + + /** @var list */ + private array $objectsBuffer = []; + + private bool $shouldReturnExistingObject = false; + + public static function instance(): self + { + return self::$instance ?? self::$instance = new self(); + } + + /** + * @param callable():iterable $dataProviderResult + */ + public function addDataset(string $className, string $methodName, callable $dataProviderResult): void + { + $this->shouldReturnExistingObject = false; + + $dataProviderResult = $dataProviderResult(); + + if (!is_array($dataProviderResult)) { + $dataProviderResult = iterator_to_array($dataProviderResult); + } + + $testCaseContext = $this->testCaseContext($className, $methodName); + $this->datasets[$testCaseContext] = $dataProviderResult; + + $this->shouldReturnExistingObject = true; + } + + /** + * @template T of object + * + * @param PersistentObjectFactory $factory + * + * @return ($factory is PersistentProxyObjectFactory ? T&Proxy : T) + */ + public function deferObjectCreation(PersistentObjectFactory $factory): object + { + if (!$this->shouldReturnExistingObject) { + return $this->objectsBuffer[] = ProxyGenerator::wrapFactory($factory); + } + + return array_shift($this->objectsBuffer); // @phpstan-ignore return.type + } + + public function triggerPersistenceForDataset(string $className, string $methodName, int|string $dataSetName): void + { + $testCaseContext = $this->testCaseContext($className, $methodName); + + if (!isset($this->datasets[$testCaseContext][$dataSetName])) { + return; + } + + initialize_proxy_object($this->datasets[$testCaseContext][$dataSetName]); + + unset($this->datasets[$testCaseContext][$dataSetName]); + } + + private function testCaseContext(string $className, string $methodName): string + { + return "{$className}::{$methodName}"; + } +} diff --git a/src/Persistence/PersistentProxyObjectFactory.php b/src/Persistence/PersistentProxyObjectFactory.php index 583607cde..a7ed99283 100644 --- a/src/Persistence/PersistentProxyObjectFactory.php +++ b/src/Persistence/PersistentProxyObjectFactory.php @@ -42,7 +42,7 @@ final public function create(callable|array $attributes = []): object { $configuration = Configuration::instance(); if ($configuration->inADataProvider() && $this->isPersisting()) { - return ProxyGenerator::wrapFactory($this, $attributes); + return PersistentObjectFromDataProviderRegistry::instance()->deferObjectCreation($this->with($attributes)); } return proxy(parent::create($attributes)); // @phpstan-ignore function.unresolvableReturnType diff --git a/src/Persistence/ProxyGenerator.php b/src/Persistence/ProxyGenerator.php index 27aeab986..21df22311 100644 --- a/src/Persistence/ProxyGenerator.php +++ b/src/Persistence/ProxyGenerator.php @@ -47,47 +47,37 @@ public static function wrap(object $object): Proxy return self::generateClassFor($object)::createLazyProxy(static fn() => $object); // @phpstan-ignore staticMethod.unresolvableReturnType } - /** - * @template T of object - * - * @param PersistentProxyObjectFactory $factory - * @phpstan-param Attributes $attributes - * - * @return T&Proxy - */ - public static function wrapFactory(PersistentProxyObjectFactory $factory, callable|array $attributes): Proxy - { - return self::generateClassFor($factory)::createLazyProxy(static function() use ($factory, $attributes) { // @phpstan-ignore staticMethod.notFound - if (Configuration::instance()->inADataProvider() && $factory->isPersisting()) { - throw new \LogicException('Cannot access to a persisted object from a data provider.'); - } - - return self::unwrap($factory->create($attributes)); - }); - } - /** * @template T of object * * @param PersistentObjectFactory $factory - * @phpstan-param Attributes $attributes * - * @return T + * @return ($factory is PersistentProxyObjectFactory ? T&Proxy : T) */ - public static function wrapFactoryNativeProxy(PersistentObjectFactory $factory, callable|array $attributes): object + public static function wrapFactory(PersistentObjectFactory $factory): object { + if ($factory instanceof PersistentProxyObjectFactory) { + return self::generateClassFor($factory)::createLazyProxy(static function() use ($factory) { // @phpstan-ignore staticMethod.notFound + if (Configuration::instance()->inADataProvider() && $factory->isPersisting()) { + throw new \LogicException('Cannot access to a persisted object from a data provider.'); + } + + return ProxyGenerator::unwrap($factory->create()); + }); + } + if (\PHP_VERSION_ID < 80400) { throw new \LogicException('Native proxy generation requires PHP 8.4 or higher.'); } $reflector = new \ReflectionClass($factory::class()); - return $reflector->newLazyProxy(static function() use ($factory, $attributes) { + return $reflector->newLazyProxy(static function() use ($factory) { if (Configuration::instance()->inADataProvider() && $factory->isPersisting()) { throw new \LogicException('Cannot access to a persisted object from a data provider.'); } - return $factory->create($attributes); + return $factory->create(); }); } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index 2d325b181..5919bce00 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -32,15 +32,14 @@ trait Factories #[Before(5)] public function _beforeHook(): void { - if (!FoundryExtension::isEnabled()) { - $this->_bootFoundry(); + if (FoundryExtension::isEnabled()) { + trigger_deprecation('zenstruck/foundry', '2.8', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); return; } - trigger_deprecation('zenstruck/foundry', '2.7', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); - - $this->_loadDataProvidedProxies(); // todo remove + $this->_bootFoundry(); + $this->_loadDataProvidedProxies(); } /** @@ -107,7 +106,9 @@ private function _loadDataProvidedProxies(): void return; } - $providedData = \method_exists($this, 'getProvidedData') ? $this->getProvidedData() : $this->providedData(); // @phpstan-ignore method.notFound, method.internal + $providedData = \method_exists($this, 'getProvidedData') // @phpstan-ignore function.impossibleType + ? $this->getProvidedData() // @phpstan-ignore method.notFound + : $this->providedData(); initialize_proxy_object($providedData); } diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php index 1a6f07194..0f721c817 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php @@ -29,6 +29,7 @@ use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; +use function Zenstruck\Foundry\Persistence\assert_persisted; /** * @author Nicolas PHILIPPE @@ -51,6 +52,9 @@ public function assert_it_can_create_one_object_in_data_provider(?GenericModel $ self::assertInstanceOf(Proxy::class, $providedData); self::assertNotInstanceOf(Proxy::class, ProxyGenerator::unwrap($providedData)); // asserts two proxies are not nested self::assertSame('value set in data provider', $providedData->getProp1()); + + static::proxyFactory()::assert()->count(1); + $providedData->_assertPersisted(); } public static function createOneProxyObjectInDataProvider(): iterable @@ -136,20 +140,24 @@ public static function throwsExceptionWhenCreatingObjectInDataProvider(): iterab #[RequiresPhp('>=8.4')] public function assert_it_can_create_one_object_in_data_provider_without_proxy_with_php_84(mixed $providedData): void { - static::proxyFactory()::assert()->count(1); + static::factory()::assert()->count(1); + self::assertNotInstanceOf(Proxy::class, $providedData); self::assertInstanceOf(GenericModel::class, $providedData); self::assertSame('value set in data provider', $providedData->getProp1()); + + assert_persisted($providedData); + static::factory()::assert()->count(1); } public static function createOneObjectInDataProvider(): iterable { yield 'createOne()' => [ - static::proxyFactory()::createOne(['prop1' => 'value set in data provider']), + static::factory()::createOne(['prop1' => 'value set in data provider']), ]; yield 'create()' => [ - static::proxyFactory()->create(['prop1' => 'value set in data provider']), + static::factory()->create(['prop1' => 'value set in data provider']), ]; } diff --git a/tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php b/tests/Integration/DataProvider/GenericDocumentFactoryTest.php similarity index 92% rename from tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php rename to tests/Integration/DataProvider/GenericDocumentFactoryTest.php index f18c5136b..34c0f607a 100644 --- a/tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php +++ b/tests/Integration/DataProvider/GenericDocumentFactoryTest.php @@ -27,7 +27,7 @@ #[RequiresPhpunit('>=11.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] #[IgnoreDeprecations] -final class GenericDocumentProxyFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase +final class GenericDocumentFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase { use RequiresMongo; diff --git a/tests/Integration/DataProvider/GenericEntityProxyFactoryTest.php b/tests/Integration/DataProvider/GenericEntityFactoryTest.php similarity index 92% rename from tests/Integration/DataProvider/GenericEntityProxyFactoryTest.php rename to tests/Integration/DataProvider/GenericEntityFactoryTest.php index 1f5eadb08..aa3412575 100644 --- a/tests/Integration/DataProvider/GenericEntityProxyFactoryTest.php +++ b/tests/Integration/DataProvider/GenericEntityFactoryTest.php @@ -27,7 +27,7 @@ #[RequiresPhpunit('>=11.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] #[IgnoreDeprecations] -final class GenericEntityProxyFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase +final class GenericEntityFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase { use RequiresORM; From c376deaa089c1448a520ab3d6d10333e9bd73d23 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Mon, 22 Sep 2025 21:42:08 +0200 Subject: [PATCH 4/7] chore: update docs + rector set --- UPGRADE-2.7.md | 2 +- UPGRADE-2.8.md | 53 +++++++++++++++++++ docs/index.rst | 50 +++++++++++------ phpunit-deprecation-baseline.xml | 5 +- src/Configuration.php | 2 +- src/PHPUnit/BuildStoryOnTestPrepared.php | 5 -- src/PHPUnit/FoundryExtension.php | 4 +- src/Test/Factories.php | 5 +- .../{foundry-set.php => foundry-2.7.php} | 0 utils/rector/config/foundry-2.8.php | 25 +++++++++ utils/rector/src/FoundrySetList.php | 11 +++- utils/rector/tests/AllRules/AllRulesTest.php | 2 +- 12 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 UPGRADE-2.8.md rename utils/rector/config/{foundry-set.php => foundry-2.7.php} (100%) create mode 100644 utils/rector/config/foundry-2.8.php diff --git a/UPGRADE-2.7.md b/UPGRADE-2.7.md index 88bfc072a..2a5937c55 100644 --- a/UPGRADE-2.7.md +++ b/UPGRADE-2.7.md @@ -52,7 +52,7 @@ return RectorConfig::configure() 'src', 'tests' ]) - ->withSets([FoundrySetList::REMOVE_PROXIES]) + ->withSets([FoundrySetList::FOUNDRY_2_7]) ; ``` diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md new file mode 100644 index 000000000..cca974d71 --- /dev/null +++ b/UPGRADE-2.8.md @@ -0,0 +1,53 @@ +# Migration guide from Foundry 2.7 to 2.8 + +The main feature of Foundry 2.8 is the deprecation of the `Factories` trait, in favor of the [PHPUnit extension](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#phpunit-extension) +shipped by Foundry. It was necessary to remember to add the trait in every test class. And in some cases, Foundry could +still work even if the trait wasn’t added to the test, which could lead to subtle bugs. Now, Foundry is globally enabled +once for all. + +The trait will be removed in Foundry 3.0, and the extension will be mandatory. + +> [!WARNING] +> The PHPUnit extension mechanism was introduced in PHPUnit 10. This means that Foundry 3 won't be compatible +> with PHPUnit 9 anymore (but Foundry 2 will remain compatible with PHPUnit 9). + +## How to + +> [!IMPORTANT] +> If you're still not using PHPUnit 10 or grater, there is nothing to do (yet!) + +Enable Foundry's [PHPUnit extension](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#phpunit-extension) +in your `phpunit.xml` file: + +```xml + + + + + +``` + +And then, remove all the `use Factories;` statements from your factories. + +## Rector rules + +A Rector set is available to automatically remove the usage of the trait in all your tests. + +First, you'll need to install `rector/rector`: +```shell +composer require --dev rector/rector +``` + +Then, create a `rector.php` file: + +```php +withPaths(['tests']) + ->withSets([FoundrySetList::FOUNDRY_2_8]) +; +``` diff --git a/docs/index.rst b/docs/index.rst index 9897437f4..4827c887b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1683,28 +1683,46 @@ Let's look at an example: .. _enable-foundry-in-your-testcase: -Enable Foundry in your TestCase -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Globally Enable Foundry In PHPUnit +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Add the ``Factories`` trait for tests using factories: +Add Foundry's `PHPUnit Extension`_ in your `phpunit.xml` file: -:: +.. configuration-block:: - use App\Factory\PostFactory; - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - use Zenstruck\Foundry\Test\Factories; + .. code-block:: xml - class MyTest extends WebTestCase - { - use Factories; + + + + + + +.. versionadded:: 2.8 + + The ability to globally enable Foundry with PHPUnit extension was introduced in Foundry 2.8 and requires at least + PHPUnit 10. + +.. note:: - public function test_1(): void + If you're still using PHPUnit 9, Foundry can be enabled by adding the trait ``Zenstruck\Foundry\Test\Factories`` + in each test:: + + use App\Factory\PostFactory; + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + use Zenstruck\Foundry\Test\Factories; + + class MyTest extends WebTestCase { - $post = PostFactory::createOne(); + use Factories; - // ... + public function test_something(): void + { + $post = PostFactory::createOne(); + + // ... + } } - } Database Reset ~~~~~~~~~~~~~~ @@ -1857,7 +1875,7 @@ Foundry provides a mechanism to automatically refresh inside a functional test t class MyTest extends WebTestCase { - use Factories, ResetDatabase; + use ResetDatabase; public function test_with_autorefresh(): void { @@ -2455,8 +2473,6 @@ any bundle configuration you have will not be picked up. class MyUnitTest extends TestCase { - use Factories; - public function some_test(): void { $post = PostFactory::createOne(); diff --git a/phpunit-deprecation-baseline.xml b/phpunit-deprecation-baseline.xml index 7b8e3ff88..b5ad2c3a3 100644 --- a/phpunit-deprecation-baseline.xml +++ b/phpunit-deprecation-baseline.xml @@ -12,8 +12,9 @@ - - + + + diff --git a/src/Configuration.php b/src/Configuration.php index fbe340a1c..e0e0249cb 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -149,7 +149,7 @@ public static function boot(\Closure|self $configuration): void self::$instance = $configuration; if (FoundryExtension::shouldBeEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.8', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3.'); + trigger_deprecation('zenstruck/foundry', '2.8', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.8.md to upgrade.'); } } diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php index 4e42376da..8689dbfee 100644 --- a/src/PHPUnit/BuildStoryOnTestPrepared.php +++ b/src/PHPUnit/BuildStoryOnTestPrepared.php @@ -16,7 +16,6 @@ use PHPUnit\Event; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; -use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed; /** * @internal @@ -47,10 +46,6 @@ public function notify(Event\Test\Prepared $event): void throw new \InvalidArgumentException(\sprintf('The test class "%s" must extend "%s" to use the "%s" attribute.', $test->className(), KernelTestCase::class, WithStory::class)); } - if (!FoundryExtension::isEnabled()) { - FactoriesTraitNotUsed::throwIfClassDoesNotHaveFactoriesTrait($test->className()); - } - foreach ($withStoryAttributes as $withStoryAttribute) { $withStoryAttribute->newInstance()->story::load(); } diff --git a/src/PHPUnit/FoundryExtension.php b/src/PHPUnit/FoundryExtension.php index ea18c5252..37202dc7b 100644 --- a/src/PHPUnit/FoundryExtension.php +++ b/src/PHPUnit/FoundryExtension.php @@ -74,12 +74,12 @@ public static function isEnabled(): bool } else { final class FoundryExtension { - public static function shouldBeEnabled(): bool + public static function shouldBeEnabled(): bool // @phpstan-ignore return.tooWideBool { return false; } - public static function isEnabled(): bool + public static function isEnabled(): bool // @phpstan-ignore return.tooWideBool { return false; } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index 5919bce00..2eac305e0 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -15,7 +15,6 @@ use PHPUnit\Framework\Attributes\Before; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; - use Zenstruck\Foundry\PHPUnit\FoundryExtension; use function Zenstruck\Foundry\Persistence\initialize_proxy_object; @@ -33,7 +32,7 @@ trait Factories public function _beforeHook(): void { if (FoundryExtension::isEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.8', sprintf('Trait %s is deprecated and will be removed in Foundry 3.', Factories::class)); + trigger_deprecation('zenstruck/foundry', '2.8', \sprintf('Trait %s is deprecated and will be removed in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.8.md to upgrade.', Factories::class)); return; } @@ -108,7 +107,7 @@ private function _loadDataProvidedProxies(): void $providedData = \method_exists($this, 'getProvidedData') // @phpstan-ignore function.impossibleType ? $this->getProvidedData() // @phpstan-ignore method.notFound - : $this->providedData(); + : $this->providedData(); // @phpstan-ignore method.internal initialize_proxy_object($providedData); } diff --git a/utils/rector/config/foundry-set.php b/utils/rector/config/foundry-2.7.php similarity index 100% rename from utils/rector/config/foundry-set.php rename to utils/rector/config/foundry-2.7.php diff --git a/utils/rector/config/foundry-2.8.php b/utils/rector/config/foundry-2.8.php new file mode 100644 index 000000000..5f505c91d --- /dev/null +++ b/utils/rector/config/foundry-2.8.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Rector\Config\RectorConfig; +use Rector\Removing\Rector\Class_\RemoveTraitUseRector; +use Zenstruck\Foundry\Test\Factories; + +return static function (RectorConfig $rectorConfig): void { + $rectorConfig->ruleWithConfiguration( + RemoveTraitUseRector::class, + [ + Factories::class, + ] + ); +}; diff --git a/utils/rector/src/FoundrySetList.php b/utils/rector/src/FoundrySetList.php index 86f51cf37..d406e41c2 100644 --- a/utils/rector/src/FoundrySetList.php +++ b/utils/rector/src/FoundrySetList.php @@ -13,6 +13,15 @@ final class FoundrySetList { + /** + * @deprecated use FoundrySetList::FOUNDRY_2_7 + * @var string + */ + public const REMOVE_PROXIES = self::FOUNDRY_2_7; + + /** @var string */ + public const FOUNDRY_2_7 = __DIR__.'/../config/foundry-2.7.php'; + /** @var string */ - public const REMOVE_PROXIES = __DIR__.'/../config/foundry-set.php'; + public const FOUNDRY_2_8 = __DIR__.'/../config/foundry-2.8.php'; } diff --git a/utils/rector/tests/AllRules/AllRulesTest.php b/utils/rector/tests/AllRules/AllRulesTest.php index 4a8ad451b..c0bdda4fa 100644 --- a/utils/rector/tests/AllRules/AllRulesTest.php +++ b/utils/rector/tests/AllRules/AllRulesTest.php @@ -36,6 +36,6 @@ public static function provideData(): \Iterator public function provideConfigFilePath(): string { - return __DIR__.'/../../config/foundry-set.php'; + return __DIR__.'/../../config/foundry-2.7.php'; } } From d7496e89f553281572b024c833c8fd02d2881cfc Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Sun, 9 Nov 2025 16:24:21 +0100 Subject: [PATCH 5/7] minor: change version 2.8 to 2.9 --- UPGRADE-2.8.md => UPGRADE-2.9.md | 4 ++-- docs/index.rst | 4 ++-- src/Configuration.php | 2 +- src/Test/Factories.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename UPGRADE-2.8.md => UPGRADE-2.9.md (93%) diff --git a/UPGRADE-2.8.md b/UPGRADE-2.9.md similarity index 93% rename from UPGRADE-2.8.md rename to UPGRADE-2.9.md index cca974d71..861d86d3a 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.9.md @@ -1,6 +1,6 @@ -# Migration guide from Foundry 2.7 to 2.8 +# Migration guide from Foundry 2.8 to 2.9 -The main feature of Foundry 2.8 is the deprecation of the `Factories` trait, in favor of the [PHPUnit extension](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#phpunit-extension) +The main feature of Foundry 2.9 is the deprecation of the `Factories` trait, in favor of the [PHPUnit extension](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#phpunit-extension) shipped by Foundry. It was necessary to remember to add the trait in every test class. And in some cases, Foundry could still work even if the trait wasn’t added to the test, which could lead to subtle bugs. Now, Foundry is globally enabled once for all. diff --git a/docs/index.rst b/docs/index.rst index 4827c887b..acc60d6fd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1698,9 +1698,9 @@ Add Foundry's `PHPUnit Extension`_ in your `phpunit.xml` file: -.. versionadded:: 2.8 +.. versionadded:: 2.9 - The ability to globally enable Foundry with PHPUnit extension was introduced in Foundry 2.8 and requires at least + The ability to globally enable Foundry with PHPUnit extension was introduced in Foundry 2.9 and requires at least PHPUnit 10. .. note:: diff --git a/src/Configuration.php b/src/Configuration.php index e0e0249cb..ab83c542f 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -149,7 +149,7 @@ public static function boot(\Closure|self $configuration): void self::$instance = $configuration; if (FoundryExtension::shouldBeEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.8', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.8.md to upgrade.'); + trigger_deprecation('zenstruck/foundry', '2.9', 'Not using Foundry\'s PHPUnit extension is deprecated and will throw an error in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.9.md to upgrade.'); } } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index 2eac305e0..8704c4a93 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -32,7 +32,7 @@ trait Factories public function _beforeHook(): void { if (FoundryExtension::isEnabled()) { - trigger_deprecation('zenstruck/foundry', '2.8', \sprintf('Trait %s is deprecated and will be removed in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.8.md to upgrade.', Factories::class)); + trigger_deprecation('zenstruck/foundry', '2.9', \sprintf('Trait %s is deprecated and will be removed in Foundry 3. See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.9.md to upgrade.', Factories::class)); return; } From c6f3ba7f662cb4b52b843b51891004c52dc0f07d Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Sun, 9 Nov 2025 16:34:42 +0100 Subject: [PATCH 6/7] minor: remove Factories trait usage in test when possible --- UPGRADE-2.9.md | 2 +- phpunit-deprecation-baseline.xml | 4 ++-- src/Configuration.php | 1 - src/PHPUnit/BootFoundryOnPreparationStarted.php | 1 - ...iggerDataProviderPersistenceOnTestPrepared.php | 1 - src/PHPUnit/FoundryExtension.php | 3 +-- src/PHPUnit/KernelTestCaseHelper.php | 9 +++++++++ .../PersistentObjectFromDataProviderRegistry.php | 15 ++++++++++++--- .../ParentClassWithStoryAttributeTestCase.php | 3 +-- .../Attribute/WithStory/WithStoryOnClassTest.php | 3 +-- .../Attribute/WithStory/WithStoryOnMethodTest.php | 3 +-- ...viderForServiceFactoryInKernelTestCaseTest.php | 3 --- .../DataProvider/DataProviderInUnitTest.php | 3 --- .../DataProvider/DataProviderWithInMemoryTest.php | 2 -- ...rWithPersistentFactoryAndPHP84InKernelTest.php | 2 -- ...viderWithPersistentFactoryInKernelTestCase.php | 5 +---- .../SkipWithPHPUnitExtension.php | 9 +++++++++ .../InMemory/DoctrineInMemoryDecoratorTest.php | 3 --- .../InMemory/InMemoryAttributeOnMethodTest.php | 2 -- tests/Integration/InMemory/InMemoryTest.php | 2 -- .../config/{foundry-2.8.php => foundry-2.9.php} | 2 +- utils/rector/src/FoundrySetList.php | 2 +- 22 files changed, 40 insertions(+), 40 deletions(-) rename utils/rector/config/{foundry-2.8.php => foundry-2.9.php} (89%) diff --git a/UPGRADE-2.9.md b/UPGRADE-2.9.md index 861d86d3a..94fdae932 100644 --- a/UPGRADE-2.9.md +++ b/UPGRADE-2.9.md @@ -48,6 +48,6 @@ use Zenstruck\Foundry\Utils\Rector\FoundrySetList; return RectorConfig::configure() ->withPaths(['tests']) - ->withSets([FoundrySetList::FOUNDRY_2_8]) + ->withSets([FoundrySetList::FOUNDRY_2_9]) ; ``` diff --git a/phpunit-deprecation-baseline.xml b/phpunit-deprecation-baseline.xml index b5ad2c3a3..843cddece 100644 --- a/phpunit-deprecation-baseline.xml +++ b/phpunit-deprecation-baseline.xml @@ -13,8 +13,8 @@ Foundry now leverages the native PHP lazy system to auto-refresh objects (it can be enabled with "zenstruck_foundry.enable_auto_refresh_with_lazy_objects" configuration). See https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.7.md to upgrade.]]> - - + + diff --git a/src/Configuration.php b/src/Configuration.php index ab83c542f..76dab41a0 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -22,7 +22,6 @@ use Zenstruck\Foundry\Persistence\PersistedObjectsTracker; use Zenstruck\Foundry\Persistence\PersistenceManager; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; /** * @author Kevin Bond diff --git a/src/PHPUnit/BootFoundryOnPreparationStarted.php b/src/PHPUnit/BootFoundryOnPreparationStarted.php index 56f03527a..f22316d7c 100644 --- a/src/PHPUnit/BootFoundryOnPreparationStarted.php +++ b/src/PHPUnit/BootFoundryOnPreparationStarted.php @@ -33,7 +33,6 @@ public function notify(Event\Test\PreparationStarted $event): void return; } /** @var Event\Code\TestMethod $test */ - $this->bootFoundry($test->className()); } diff --git a/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php index 50fe7ecfd..81e675725 100644 --- a/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php +++ b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php @@ -30,7 +30,6 @@ public function notify(Event\Test\Prepared $event): void return; } /** @var Event\Code\TestMethod $test */ - if (!$test->testData()->hasDataFromDataProvider() || $test->metadata()->isDataProvider()->isEmpty()) { return; } diff --git a/src/PHPUnit/FoundryExtension.php b/src/PHPUnit/FoundryExtension.php index 37202dc7b..7725f3673 100644 --- a/src/PHPUnit/FoundryExtension.php +++ b/src/PHPUnit/FoundryExtension.php @@ -25,8 +25,7 @@ * @internal * @author Nicolas PHILIPPE */ - -if (interface_exists(Runner\Extension\Extension::class)) { +if (\interface_exists(Runner\Extension\Extension::class)) { final class FoundryExtension implements Runner\Extension\Extension { private static bool $enabled = false; diff --git a/src/PHPUnit/KernelTestCaseHelper.php b/src/PHPUnit/KernelTestCaseHelper.php index 12f7ff4ca..f0485e696 100644 --- a/src/PHPUnit/KernelTestCaseHelper.php +++ b/src/PHPUnit/KernelTestCaseHelper.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Zenstruck\Foundry\PHPUnit; use PHPUnit\Framework\TestCase; diff --git a/src/Persistence/PersistentObjectFromDataProviderRegistry.php b/src/Persistence/PersistentObjectFromDataProviderRegistry.php index 32dea858c..6732dc258 100644 --- a/src/Persistence/PersistentObjectFromDataProviderRegistry.php +++ b/src/Persistence/PersistentObjectFromDataProviderRegistry.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Zenstruck\Foundry\Persistence; /** @@ -56,8 +65,8 @@ public function addDataset(string $className, string $methodName, callable $data $dataProviderResult = $dataProviderResult(); - if (!is_array($dataProviderResult)) { - $dataProviderResult = iterator_to_array($dataProviderResult); + if (!\is_array($dataProviderResult)) { + $dataProviderResult = \iterator_to_array($dataProviderResult); } $testCaseContext = $this->testCaseContext($className, $methodName); @@ -79,7 +88,7 @@ public function deferObjectCreation(PersistentObjectFactory $factory): object return $this->objectsBuffer[] = ProxyGenerator::wrapFactory($factory); } - return array_shift($this->objectsBuffer); // @phpstan-ignore return.type + return \array_shift($this->objectsBuffer); // @phpstan-ignore return.type } public function triggerPersistenceForDataset(string $className, string $methodName, int|string $dataSetName): void diff --git a/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php b/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php index 0a3369277..553fef5e0 100644 --- a/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php +++ b/tests/Integration/Attribute/WithStory/ParentClassWithStoryAttributeTestCase.php @@ -15,7 +15,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Stories\EntityStory; use Zenstruck\Foundry\Tests\Integration\RequiresORM; @@ -26,5 +25,5 @@ #[WithStory(EntityStory::class)] abstract class ParentClassWithStoryAttributeTestCase extends KernelTestCase { - use Factories, RequiresORM, ResetDatabase; + use RequiresORM, ResetDatabase; } diff --git a/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php b/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php index f88ab6c8e..90ce7e001 100644 --- a/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php +++ b/tests/Integration/Attribute/WithStory/WithStoryOnClassTest.php @@ -19,7 +19,6 @@ 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; @@ -35,7 +34,7 @@ #[WithStory(EntityStory::class)] final class WithStoryOnClassTest extends KernelTestCase { - use Factories, RequiresORM, ResetDatabase; + use RequiresORM, ResetDatabase; #[Test] public function can_use_story_in_attribute(): void diff --git a/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php b/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php index f1df60672..8e7bd6aed 100644 --- a/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php +++ b/tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php @@ -19,7 +19,6 @@ 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; @@ -35,7 +34,7 @@ #[RequiresPhpunitExtension(FoundryExtension::class)] final class WithStoryOnMethodTest extends KernelTestCase { - use Factories, RequiresORM, ResetDatabase; + use RequiresORM, ResetDatabase; #[Test] #[WithStory(EntityStory::class)] diff --git a/tests/Integration/DataProvider/DataProviderForServiceFactoryInKernelTestCaseTest.php b/tests/Integration/DataProvider/DataProviderForServiceFactoryInKernelTestCaseTest.php index 579e34db3..cdb344787 100644 --- a/tests/Integration/DataProvider/DataProviderForServiceFactoryInKernelTestCaseTest.php +++ b/tests/Integration/DataProvider/DataProviderForServiceFactoryInKernelTestCaseTest.php @@ -20,7 +20,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; use Zenstruck\Foundry\Tests\Fixture\Object1; @@ -34,8 +33,6 @@ #[RequiresPhpunitExtension(FoundryExtension::class)] final class DataProviderForServiceFactoryInKernelTestCaseTest extends KernelTestCase { - use Factories; - #[Test] #[DataProvider('createObjectFromServiceFactoryInDataProvider')] public function it_can_create_one_object_in_data_provider(?Object1 $providedData, string $expected): void diff --git a/tests/Integration/DataProvider/DataProviderInUnitTest.php b/tests/Integration/DataProvider/DataProviderInUnitTest.php index db2ec5a72..aad8d9640 100644 --- a/tests/Integration/DataProvider/DataProviderInUnitTest.php +++ b/tests/Integration/DataProvider/DataProviderInUnitTest.php @@ -21,7 +21,6 @@ use PHPUnit\Framework\TestCase; use Zenstruck\Foundry\Persistence\ProxyGenerator; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Tests\Fixture\Entity\GenericEntity; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericProxyEntityFactory; @@ -40,8 +39,6 @@ #[RequiresPhpunitExtension(FoundryExtension::class)] final class DataProviderInUnitTest extends TestCase { - use Factories; - #[Test] #[DataProvider('createObjectWithObjectFactoryInDataProvider')] public function assert_it_can_create_object_with_object_factory_in_data_provider(mixed $providedData, mixed $expectedData): void diff --git a/tests/Integration/DataProvider/DataProviderWithInMemoryTest.php b/tests/Integration/DataProvider/DataProviderWithInMemoryTest.php index 824a6e5f9..d570d0163 100644 --- a/tests/Integration/DataProvider/DataProviderWithInMemoryTest.php +++ b/tests/Integration/DataProvider/DataProviderWithInMemoryTest.php @@ -23,7 +23,6 @@ use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Persistence\ProxyGenerator; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Entity\Contact; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\ContactFactory; @@ -39,7 +38,6 @@ #[RequiresPhpunitExtension(FoundryExtension::class)] final class DataProviderWithInMemoryTest extends KernelTestCase { - use Factories; use RequiresORM; // needed to use the entity manager use ResetDatabase; diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php index e0c918556..e4b0d53f8 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php @@ -21,7 +21,6 @@ use PHPUnit\Framework\Attributes\Test; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 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\Model\GenericModel; @@ -36,7 +35,6 @@ #[RequiresEnvironmentVariable('USE_PHP_84_LAZY_OBJECTS', '1')] final class DataProviderWithPersistentFactoryAndPHP84InKernelTest extends KernelTestCase { - use Factories; use ResetDatabase; #[Test] diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php index 0f721c817..8409970d9 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php @@ -14,7 +14,6 @@ namespace Zenstruck\Foundry\Tests\Integration\DataProvider; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\RequiresPhpunit; use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; @@ -25,10 +24,10 @@ use Zenstruck\Foundry\Persistence\Proxy; use Zenstruck\Foundry\Persistence\ProxyGenerator; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; + use function Zenstruck\Foundry\Persistence\assert_persisted; /** @@ -37,10 +36,8 @@ */ #[RequiresPhpunit('>=11.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] -#[IgnoreDeprecations] abstract class DataProviderWithPersistentFactoryInKernelTestCase extends KernelTestCase { - use Factories; use ResetDatabase; #[Test] diff --git a/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php index 64ca8f8b9..f489d99cf 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php +++ b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage; use PHPUnit\Framework\Attributes\Before; diff --git a/tests/Integration/InMemory/DoctrineInMemoryDecoratorTest.php b/tests/Integration/InMemory/DoctrineInMemoryDecoratorTest.php index c36258021..c5aeacbe0 100644 --- a/tests/Integration/InMemory/DoctrineInMemoryDecoratorTest.php +++ b/tests/Integration/InMemory/DoctrineInMemoryDecoratorTest.php @@ -20,7 +20,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\InMemory\AsInMemoryTest; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Tests\Fixture\Entity\WithEmbeddableEntity; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Category\CategoryFactory; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Contact\ContactFactory; @@ -41,8 +40,6 @@ #[AsInMemoryTest] final class DoctrineInMemoryDecoratorTest extends KernelTestCase { - use Factories; - /** * @test */ diff --git a/tests/Integration/InMemory/InMemoryAttributeOnMethodTest.php b/tests/Integration/InMemory/InMemoryAttributeOnMethodTest.php index 9444f67c7..b2cf535db 100644 --- a/tests/Integration/InMemory/InMemoryAttributeOnMethodTest.php +++ b/tests/Integration/InMemory/InMemoryAttributeOnMethodTest.php @@ -20,7 +20,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\InMemory\AsInMemoryTest; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Entity\Address; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\Address\AddressFactory; @@ -34,7 +33,6 @@ #[RequiresPhpunitExtension(FoundryExtension::class)] final class InMemoryAttributeOnMethodTest extends KernelTestCase { - use Factories; use RequiresORM; use ResetDatabase; diff --git a/tests/Integration/InMemory/InMemoryTest.php b/tests/Integration/InMemory/InMemoryTest.php index 272c7df4d..a43f0f1ff 100644 --- a/tests/Integration/InMemory/InMemoryTest.php +++ b/tests/Integration/InMemory/InMemoryTest.php @@ -20,7 +20,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\InMemory\AsInMemoryTest; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Entity\Address; use Zenstruck\Foundry\Tests\Fixture\Entity\Category; @@ -41,7 +40,6 @@ #[AsInMemoryTest] final class InMemoryTest extends KernelTestCase { - use Factories; use RequiresORM; use ResetDatabase; diff --git a/utils/rector/config/foundry-2.8.php b/utils/rector/config/foundry-2.9.php similarity index 89% rename from utils/rector/config/foundry-2.8.php rename to utils/rector/config/foundry-2.9.php index 5f505c91d..99626b9db 100644 --- a/utils/rector/config/foundry-2.8.php +++ b/utils/rector/config/foundry-2.9.php @@ -15,7 +15,7 @@ use Rector\Removing\Rector\Class_\RemoveTraitUseRector; use Zenstruck\Foundry\Test\Factories; -return static function (RectorConfig $rectorConfig): void { +return static function(RectorConfig $rectorConfig): void { $rectorConfig->ruleWithConfiguration( RemoveTraitUseRector::class, [ diff --git a/utils/rector/src/FoundrySetList.php b/utils/rector/src/FoundrySetList.php index d406e41c2..aceccf7a2 100644 --- a/utils/rector/src/FoundrySetList.php +++ b/utils/rector/src/FoundrySetList.php @@ -23,5 +23,5 @@ final class FoundrySetList public const FOUNDRY_2_7 = __DIR__.'/../config/foundry-2.7.php'; /** @var string */ - public const FOUNDRY_2_8 = __DIR__.'/../config/foundry-2.8.php'; + public const FOUNDRY_2_9 = __DIR__.'/../config/foundry-2.9.php'; } From 5ec618e48e8a7b217db841e4c40055e9b0e351ab Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Mon, 10 Nov 2025 12:49:35 +0100 Subject: [PATCH 7/7] refactor: ony run twice data providers using Foundry --- src/Exception/FoundryNotBooted.php | 2 +- src/FactoryCollection.php | 4 + .../BootFoundryOnDataProviderMethodCalled.php | 7 - ...ownFoundryOnDataProviderMethodFinished.php | 7 + src/Persistence/IsProxy.php | 3 +- ...rsistentObjectFromDataProviderRegistry.php | 60 ++++-- src/Persistence/functions.php | 4 +- src/Test/Factories.php | 4 +- ...viderWithPersistentDocumentFactoryTest.php | 47 +++++ ...oviderWithPersistentEntityFactoryTest.php} | 42 +---- ...ProviderWithPersistentFactoryTestCase.php} | 174 +++++++++--------- ...ithProxyPersistentDocumentFactoryTest.php} | 9 +- ...erWithProxyPersistentEntityFactoryTest.php | 86 +++++++++ .../DataProvider/GenericEntityFactoryTest.php | 43 ----- 14 files changed, 289 insertions(+), 203 deletions(-) create mode 100644 tests/Integration/DataProvider/DataProviderWithPersistentDocumentFactoryTest.php rename tests/Integration/DataProvider/{DataProviderWithPersistentFactoryAndPHP84InKernelTest.php => DataProviderWithPersistentEntityFactoryTest.php} (55%) rename tests/Integration/DataProvider/{DataProviderWithPersistentFactoryInKernelTestCase.php => DataProviderWithPersistentFactoryTestCase.php} (52%) rename tests/Integration/DataProvider/{GenericDocumentFactoryTest.php => DataProviderWithProxyPersistentDocumentFactoryTest.php} (78%) create mode 100644 tests/Integration/DataProvider/DataProviderWithProxyPersistentEntityFactoryTest.php delete mode 100644 tests/Integration/DataProvider/GenericEntityFactoryTest.php diff --git a/src/Exception/FoundryNotBooted.php b/src/Exception/FoundryNotBooted.php index d6fa3fc8c..353058bae 100644 --- a/src/Exception/FoundryNotBooted.php +++ b/src/Exception/FoundryNotBooted.php @@ -20,7 +20,7 @@ final class FoundryNotBooted extends \LogicException { public function __construct() { - $message = FoundryExtension::isEnabled() + $message = FoundryExtension::shouldBeEnabled() ? 'Foundry is not yet booted. Ensure ZenstruckFoundryBundle is enabled. If in a test, ensure Foundry\'s PHPUnit extension is enabled.' : 'Foundry is not yet booted. Ensure ZenstruckFoundryBundle is enabled. If in a test, ensure your TestCase has the Factories trait.'; diff --git a/src/FactoryCollection.php b/src/FactoryCollection.php index e6a1e47d4..8da7d9d5c 100644 --- a/src/FactoryCollection.php +++ b/src/FactoryCollection.php @@ -125,6 +125,10 @@ public static function range(Factory $factory, int $min, int $max): self throw new \InvalidArgumentException('Min must be less than max.'); } + if ($factory instanceof PersistentObjectFactory && $factory->isPersisting() && Configuration::instance()->inADataProvider()) { + throw new \InvalidArgumentException('Using randomized "range" factory in data provider is not supported.'); + } + return new self($factory, static fn() => \array_fill(0, \mt_rand($min, $max), [])); } diff --git a/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php index f967a92c5..da7677ac5 100644 --- a/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php +++ b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php @@ -18,7 +18,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\InMemory\AsInMemoryTest; -use Zenstruck\Foundry\Persistence\PersistentObjectFromDataProviderRegistry; use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; use Zenstruck\Foundry\Test\UnitTestConfig; @@ -32,12 +31,6 @@ public function notify(Event\Test\DataProviderMethodCalled $event): void { $this->bootFoundryForDataProvider($event->testMethod()->className()); - PersistentObjectFromDataProviderRegistry::instance()->addDataset( - $event->testMethod()->className(), - $event->testMethod()->methodName(), - "{$event->dataProviderMethod()->className()}::{$event->dataProviderMethod()->methodName()}"(...) // @phpstan-ignore callable.nonCallable - ); - $testMethod = $event->testMethod(); if (AsInMemoryTest::shouldEnableInMemory($testMethod->className(), $testMethod->methodName())) { diff --git a/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php index 690ecfa7f..774448b16 100644 --- a/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php +++ b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php @@ -15,6 +15,7 @@ use PHPUnit\Event; use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\Persistence\PersistentObjectFromDataProviderRegistry; use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; /** @@ -25,6 +26,12 @@ final class ShutdownFoundryOnDataProviderMethodFinished implements Event\Test\Da { public function notify(Event\Test\DataProviderMethodFinished $event): void { + PersistentObjectFromDataProviderRegistry::instance()->storeDatasetIfFoundryWasUsedInDataProvider( + $event->testMethod()->className(), + $event->testMethod()->methodName(), + ...$event->calledMethods(), + ); + KernelTestCaseHelper::tearDownClass($event->testMethod()->className()); Configuration::shutdown(); diff --git a/src/Persistence/IsProxy.php b/src/Persistence/IsProxy.php index be6351864..5d06eebef 100644 --- a/src/Persistence/IsProxy.php +++ b/src/Persistence/IsProxy.php @@ -16,6 +16,7 @@ use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\Exception\PersistenceNotAvailable; use Zenstruck\Foundry\Object\Hydrator; +use Zenstruck\Foundry\Persistence\Exception\NoPersistenceStrategy; use Zenstruck\Foundry\Persistence\Exception\ObjectNoLongerExist; /** @@ -152,7 +153,7 @@ private function _autoRefresh(): void // we don't want that "transparent" calls to _refresh() to trigger a PersistenceNotAvailable exception // or a RefreshObjectFailed exception when the object was deleted $this->_refresh(); - } catch (PersistenceNotAvailable|ObjectNoLongerExist) { + } catch (PersistenceNotAvailable|ObjectNoLongerExist|NoPersistenceStrategy) { } } diff --git a/src/Persistence/PersistentObjectFromDataProviderRegistry.php b/src/Persistence/PersistentObjectFromDataProviderRegistry.php index 6732dc258..ed4fb2726 100644 --- a/src/Persistence/PersistentObjectFromDataProviderRegistry.php +++ b/src/Persistence/PersistentObjectFromDataProviderRegistry.php @@ -11,11 +11,13 @@ namespace Zenstruck\Foundry\Persistence; +use PHPUnit\Event\Code\ClassMethod; + /** - * If a persistent object has been created in a data provider, we need to initialize the proxy object, + * If a persistent object has been created in a data provider, we need to initialize the lazy object, * which will trigger the object to be persisted. * - * Otherwise, such test would not pass: + * Otherwise, such a test would not pass: * ```php * #[DataProvider('provide')] * public function testSomething(MyEntity $entity): void @@ -31,9 +33,12 @@ * * Sadly, this cannot be done directly a subscriber, since PHPUnit does not give access to the actual tests instances. * - * This class is highly hacky! - * We collect all the "datasets" and we trigger the persistence for each one before the test is executed. - * This means that de data providers are called twice. + * ⚠️ This class is highly hacky! + * + * If we detect that a persisting object was created in a data provider, we collect the "datasets" of the test, + * and we trigger the persistence of these objects before the test is executed. + * + * This means that the data providers using Foundry are called twice. * To prevent the persisted object from being different from the one returned by the data provider, we use a "buffer" so * that we can return the same object for each data provider call. * @@ -49,30 +54,39 @@ final class PersistentObjectFromDataProviderRegistry /** @var list */ private array $objectsBuffer = []; - private bool $shouldReturnExistingObject = false; + private bool $shouldReturnObjectFromBuffer = false; public static function instance(): self { return self::$instance ?? self::$instance = new self(); } - /** - * @param callable():iterable $dataProviderResult - */ - public function addDataset(string $className, string $methodName, callable $dataProviderResult): void + public function storeDatasetIfFoundryWasUsedInDataProvider(string $className, string $methodName, ClassMethod ...$calledMethods): void { - $this->shouldReturnExistingObject = false; + if (count($this->objectsBuffer) === 0) { + return; + } + + $this->shouldReturnObjectFromBuffer = true; + + $testCaseContext = $this->testCaseContext($className, $methodName); + $this->datasets[$testCaseContext] = []; + + foreach ($calledMethods as $calledMethod) { + $dataProviderResult = "{$calledMethod->className()}::{$calledMethod->methodName()}"(); // @phpstan-ignore callable.nonCallable - $dataProviderResult = $dataProviderResult(); + if (!\is_array($dataProviderResult)) { + $dataProviderResult = \iterator_to_array($dataProviderResult); + } - if (!\is_array($dataProviderResult)) { - $dataProviderResult = \iterator_to_array($dataProviderResult); + $this->datasets[$testCaseContext] = [...$this->datasets[$testCaseContext], ...$dataProviderResult]; } - $testCaseContext = $this->testCaseContext($className, $methodName); - $this->datasets[$testCaseContext] = $dataProviderResult; + $this->shouldReturnObjectFromBuffer = false; - $this->shouldReturnExistingObject = true; + if (count($this->objectsBuffer) !== 0) { // @phpstan-ignore notIdentical.alwaysTrue + throw new \InvalidArgumentException("No object found. Hint: make sure you're not creating a randomized number of objects with Foundry in a data provider, as they are not supported."); + } } /** @@ -84,10 +98,18 @@ public function addDataset(string $className, string $methodName, callable $data */ public function deferObjectCreation(PersistentObjectFactory $factory): object { - if (!$this->shouldReturnExistingObject) { + if (!$factory->isPersisting()) { + return $factory->create(); + } + + if (!$this->shouldReturnObjectFromBuffer) { return $this->objectsBuffer[] = ProxyGenerator::wrapFactory($factory); } + if (count($this->objectsBuffer) === 0) { + throw new \InvalidArgumentException("No object found. Hint: make sure you're not creating a randomized number of objects with Foundry in a data provider, as they are not supported."); + } + return \array_shift($this->objectsBuffer); // @phpstan-ignore return.type } @@ -99,7 +121,7 @@ public function triggerPersistenceForDataset(string $className, string $methodNa return; } - initialize_proxy_object($this->datasets[$testCaseContext][$dataSetName]); + initialize_lazy_object($this->datasets[$testCaseContext][$dataSetName]); unset($this->datasets[$testCaseContext][$dataSetName]); } diff --git a/src/Persistence/functions.php b/src/Persistence/functions.php index 50d0768a6..117e6fa95 100644 --- a/src/Persistence/functions.php +++ b/src/Persistence/functions.php @@ -230,7 +230,7 @@ function assert_not_persisted(object $object, string $message = '{entity} is per /** * @internal */ -function initialize_proxy_object(mixed $what): void +function initialize_lazy_object(mixed $what): void { if ( \PHP_VERSION_ID >= 80400 @@ -244,7 +244,7 @@ function initialize_proxy_object(mixed $what): void match (true) { $what instanceof Proxy => $what->_initializeLazyObject(), - \is_array($what) => \array_map(initialize_proxy_object(...), $what), + \is_array($what) => \array_map(initialize_lazy_object(...), $what), default => true, // do nothing }; } diff --git a/src/Test/Factories.php b/src/Test/Factories.php index 8704c4a93..6b5f80291 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -17,7 +17,7 @@ use Zenstruck\Foundry\Configuration; use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use function Zenstruck\Foundry\Persistence\initialize_proxy_object; +use function Zenstruck\Foundry\Persistence\initialize_lazy_object; /** * @author Kevin Bond @@ -109,6 +109,6 @@ private function _loadDataProvidedProxies(): void ? $this->getProvidedData() // @phpstan-ignore method.notFound : $this->providedData(); // @phpstan-ignore method.internal - initialize_proxy_object($providedData); + initialize_lazy_object($providedData); } } diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentDocumentFactoryTest.php b/tests/Integration/DataProvider/DataProviderWithPersistentDocumentFactoryTest.php new file mode 100644 index 000000000..e8d0a4032 --- /dev/null +++ b/tests/Integration/DataProvider/DataProviderWithPersistentDocumentFactoryTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Integration\DataProvider; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Document\GenericDocumentFactory; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; +use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; +use Zenstruck\Foundry\Tests\Integration\RequiresMongo; + +/** + * @author Nicolas PHILIPPE + * @requires PHPUnit >=12 + */ +#[RequiresPhpunit('>=12')] +#[RequiresPhp('>=8.4')] +#[RequiresPhpunitExtension(FoundryExtension::class)] +#[RequiresEnvironmentVariable('USE_PHP_84_LAZY_OBJECTS', '1')] +final class DataProviderWithPersistentDocumentFactoryTest extends DataProviderWithPersistentFactoryTestCase +{ + use RequiresMongo; + + protected static function factory(): PersistentObjectFactory + { + return GenericDocumentFactory::new(); + } +} diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php b/tests/Integration/DataProvider/DataProviderWithPersistentEntityFactoryTest.php similarity index 55% rename from tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php rename to tests/Integration/DataProvider/DataProviderWithPersistentEntityFactoryTest.php index e4b0d53f8..74a732e77 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentEntityFactoryTest.php @@ -20,10 +20,12 @@ use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; use PHPUnit\Framework\Attributes\Test; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\PHPUnit\FoundryExtension; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; +use Zenstruck\Foundry\Tests\Integration\RequiresORM; /** * @author Nicolas PHILIPPE @@ -33,9 +35,9 @@ #[RequiresPhp('>=8.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] #[RequiresEnvironmentVariable('USE_PHP_84_LAZY_OBJECTS', '1')] -final class DataProviderWithPersistentFactoryAndPHP84InKernelTest extends KernelTestCase +final class DataProviderWithPersistentEntityFactoryTest extends DataProviderWithPersistentFactoryTestCase { - use ResetDatabase; + use RequiresORM; #[Test] #[DataProvider('createOneObjectInDataProvider')] @@ -45,7 +47,6 @@ public function assert_it_can_create_one_object_in_data_provider(?GenericModel $ self::assertNotNull($providedData); self::assertFalse((new \ReflectionClass($providedData))->isUninitializedLazyObject($providedData)); - self::assertSame('value set in data provider', $providedData->getProp1()); } public static function createOneObjectInDataProvider(): iterable @@ -53,41 +54,10 @@ public static function createOneObjectInDataProvider(): iterable yield 'createOne()' => [ GenericEntityFactory::createOne(['prop1' => 'value set in data provider']), ]; - - yield 'create()' => [ - GenericEntityFactory::new()->create(['prop1' => 'value set in data provider']), - ]; } - #[Test] - #[DataProvider('createMultipleObjectsInDataProvider')] - public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void + protected static function factory(): PersistentObjectFactory { - self::assertIsArray($providedData); - GenericEntityFactory::assert()->count(2); - - foreach ($providedData as $providedDatum) { - self::assertFalse((new \ReflectionClass($providedDatum))->isUninitializedLazyObject($providedDatum)); - } - - self::assertSame('prop 1', $providedData[0]->getProp1()); - self::assertSame('prop 2', $providedData[1]->getProp1()); - } - - public static function createMultipleObjectsInDataProvider(): iterable - { - yield 'createSequence()' => [ - GenericEntityFactory::createSequence([ - ['prop1' => 'prop 1'], - ['prop1' => 'prop 2'], - ]), - ]; - - yield 'FactoryCollection::create()' => [ - GenericEntityFactory::new()->sequence([ - ['prop1' => 'prop 1'], - ['prop1' => 'prop 2'], - ])->create(), - ]; + return GenericEntityFactory::new(); } } diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php similarity index 52% rename from tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php rename to tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php index 8409970d9..f12820601 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php @@ -1,67 +1,44 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Zenstruck\Foundry\Tests\Integration\DataProvider; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresPhp; -use PHPUnit\Framework\Attributes\RequiresPhpunit; -use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; use PHPUnit\Framework\Attributes\Test; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Persistence\PersistentObjectFactory; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Persistence\Proxy; use Zenstruck\Foundry\Persistence\ProxyGenerator; -use Zenstruck\Foundry\PHPUnit\FoundryExtension; use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; - use function Zenstruck\Foundry\Persistence\assert_persisted; -/** - * @author Nicolas PHILIPPE - * @requires PHPUnit >=11.4 - */ -#[RequiresPhpunit('>=11.4')] -#[RequiresPhpunitExtension(FoundryExtension::class)] -abstract class DataProviderWithPersistentFactoryInKernelTestCase extends KernelTestCase +abstract class DataProviderWithPersistentFactoryTestCase extends KernelTestCase { use ResetDatabase; #[Test] - #[DataProvider('createOneProxyObjectInDataProvider')] + #[DataProvider('createOneObjectInDataProvider')] public function assert_it_can_create_one_object_in_data_provider(?GenericModel $providedData): void { - static::proxyFactory()::assert()->count(1); + static::factory()::assert()->count(1); - self::assertInstanceOf(Proxy::class, $providedData); - self::assertNotInstanceOf(Proxy::class, ProxyGenerator::unwrap($providedData)); // asserts two proxies are not nested + self::assertNotNull($providedData); self::assertSame('value set in data provider', $providedData->getProp1()); - static::proxyFactory()::assert()->count(1); - $providedData->_assertPersisted(); + assert_persisted($providedData); } - public static function createOneProxyObjectInDataProvider(): iterable + public static function createOneObjectInDataProvider(): iterable { yield 'createOne()' => [ - static::proxyFactory()::createOne(['prop1' => 'value set in data provider']), + static::factory()::createOne(['prop1' => 'value set in data provider']), ]; yield 'create()' => [ - static::proxyFactory()->create(['prop1' => 'value set in data provider']), + static::factory()->create(['prop1' => 'value set in data provider']), ]; } @@ -70,7 +47,8 @@ public static function createOneProxyObjectInDataProvider(): iterable public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void { self::assertIsArray($providedData); - static::proxyFactory()::assert()->count(2); + static::factory()::assert()->count(2); + self::assertSame('prop 1', $providedData[0]->getProp1()); self::assertSame('prop 2', $providedData[1]->getProp1()); } @@ -78,54 +56,98 @@ public function assert_it_can_create_multiple_objects_in_data_provider(?array $p public static function createMultipleObjectsInDataProvider(): iterable { yield 'createSequence()' => [ - static::proxyFactory()::createSequence([ + static::factory()::createSequence([ ['prop1' => 'prop 1'], ['prop1' => 'prop 2'], ]), ]; - yield 'FactoryCollection::create()' => [ - static::proxyFactory()->sequence([ + yield 'sequence()->create()' => [ + static::factory()->sequence([ ['prop1' => 'prop 1'], ['prop1' => 'prop 2'], ])->create(), ]; + + yield 'createMany()' => [ + static::factory()::createMany(2, static fn(int $i) => ['prop1' => "prop $i"]), + ]; + + yield 'many()->create()' => [ + static::factory()->many(2)->create(static fn(int $i) => ['prop1' => "prop $i"]), + ]; } #[Test] - #[DataProvider('useGetterOnProxyObjectCreatedInDataProvider')] - public function assert_using_getter_proxy_object_created_in_a_data_provider_throws(?\Throwable $e): void + #[DataProvider('dataProviderRetuningArray')] + public function assert_it_can_use_data_provider_returning_array(?GenericModel $providedData): void { - self::assertInstanceOf(\LogicException::class, $e); - self::assertStringStartsWith('Cannot access to a persisted object from a data provider.', $e->getMessage()); + static::factory()::assert()->count(1); + + self::assertNotNull($providedData); + self::assertSame('value set in data provider', $providedData->getProp1()); } - public static function useGetterOnProxyObjectCreatedInDataProvider(): iterable + public static function dataProviderRetuningArray(): array { - try { - static::proxyFactory()::createOne()->getProp1(); - } catch (\Throwable $e) { + return [ + [ + static::factory()::createOne(['prop1' => 'value set in data provider']), + ], + [ + static::factory()->create(['prop1' => 'value set in data provider']), + ] + ]; + } + + #[Test] + #[DataProvider('multipleDataProviders1')] + #[DataProvider('multipleDataProviders2')] + public function assert_it_can_use_multiple_data_providers(array $providedData, int $expectedCount): void + { + static::factory()::assert()->count($expectedCount); + + foreach ($providedData as $providedDatum) { + self::assertNotNull($providedDatum); + self::assertSame('value set in data provider', $providedDatum->getProp1()); } + } - yield [$e ?? null]; + public static function multipleDataProviders1(): iterable + { + yield [ + static::factory()::createMany(1, ['prop1' => 'value set in data provider']), + 1 + ]; + } + + public static function multipleDataProviders2(): iterable + { + yield [ + static::factory()::createMany(2, ['prop1' => 'value set in data provider']), + 2 + ]; } #[Test] - #[DataProvider('throwsExceptionWhenCreatingObjectInDataProvider')] - #[RequiresPhp('<8.4')] - public function it_throws_when_creating_persisted_object_with_non_proxy_factory_in_data_provider_without_php_84(?\Throwable $e): void + #[DataProvider('dataProviderUsingRandomRanges')] + public function assert_data_provider_throws_when_using_range_in_data_provider(?\Throwable $throwable): void { - self::assertInstanceOf(\LogicException::class, $e); - self::assertStringStartsWith( - 'Cannot create object in a data provider for non-proxy factories.', - $e->getMessage() - ); + self::assertInstanceOf(\InvalidArgumentException::class, $throwable); + self::assertSame('Using randomized "range" factory in data provider is not supported.', $throwable->getMessage()); } - public static function throwsExceptionWhenCreatingObjectInDataProvider(): iterable + public static function dataProviderUsingRandomRanges(): iterable { try { - static::factory()::createOne(); + GenericEntityFactory::createRange(1, 2); + } catch (\Throwable $e) { + } + + yield [$e ?? null]; + + try { + GenericEntityFactory::new()->range(1, 2); } catch (\Throwable $e) { } @@ -133,29 +155,21 @@ public static function throwsExceptionWhenCreatingObjectInDataProvider(): iterab } #[Test] - #[DataProvider('createOneObjectInDataProvider')] - #[RequiresPhp('>=8.4')] - public function assert_it_can_create_one_object_in_data_provider_without_proxy_with_php_84(mixed $providedData): void + #[DataProvider('useGetterOnProxyObjectCreatedInDataProvider')] + public function assert_using_getter_proxy_object_created_in_a_data_provider_throws(?\Throwable $e): void { - static::factory()::assert()->count(1); - - self::assertNotInstanceOf(Proxy::class, $providedData); - self::assertInstanceOf(GenericModel::class, $providedData); - self::assertSame('value set in data provider', $providedData->getProp1()); - - assert_persisted($providedData); - static::factory()::assert()->count(1); + self::assertInstanceOf(\LogicException::class, $e); + self::assertStringStartsWith('Cannot access to a persisted object from a data provider.', $e->getMessage()); } - public static function createOneObjectInDataProvider(): iterable + public static function useGetterOnProxyObjectCreatedInDataProvider(): iterable { - yield 'createOne()' => [ - static::factory()::createOne(['prop1' => 'value set in data provider']), - ]; + try { + static::factory()::createOne()->getProp1(); + } catch (\Throwable $e) { + } - yield 'create()' => [ - static::factory()->create(['prop1' => 'value set in data provider']), - ]; + yield [$e ?? null]; } #[Test] @@ -170,23 +184,13 @@ public function assert_it_can_use_getter_on_non_persisted_object_created_in_data public static function useGetterOnObjectCreatedInDataProvider(): iterable { yield 'object factory' => [Object1Factory::createOne()->getProp1(), 'router-constructor']; - yield 'persistent factory' => [static::factory()::new()->withoutPersisting()->create()->getProp1(), 'default1']; + yield 'persistent factory' => [static::factory()->withoutPersisting()->create()->getProp1(), 'default1']; yield 'persistent factory using many' => [ - static::factory()::new()->withoutPersisting()->many(1)->create()[0]->getProp1(), - 'default1', - ]; - yield 'proxy factory' => [static::proxyFactory()::new()->withoutPersisting()->create()->_real(withAutoRefresh: false)->getProp1(), 'default1']; - yield 'proxy factory using many' => [ - static::proxyFactory()::new()->withoutPersisting()->many(1)->create()[0]->_real(withAutoRefresh: false)->getProp1(), + static::factory()->withoutPersisting()->many(1)->create()[0]->getProp1(), 'default1', ]; } - /** - * @return PersistentProxyObjectFactory - */ - abstract protected static function proxyFactory(): PersistentProxyObjectFactory; - /** * @return PersistentObjectFactory */ diff --git a/tests/Integration/DataProvider/GenericDocumentFactoryTest.php b/tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php similarity index 78% rename from tests/Integration/DataProvider/GenericDocumentFactoryTest.php rename to tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php index 34c0f607a..138d66d0a 100644 --- a/tests/Integration/DataProvider/GenericDocumentFactoryTest.php +++ b/tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php @@ -27,17 +27,12 @@ #[RequiresPhpunit('>=11.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] #[IgnoreDeprecations] -final class GenericDocumentFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase +final class DataProviderWithProxyPersistentDocumentFactoryTest extends DataProviderWithPersistentFactoryTestCase { use RequiresMongo; - protected static function proxyFactory(): GenericProxyDocumentFactory + protected static function factory(): GenericProxyDocumentFactory { return GenericProxyDocumentFactory::new(); } - - protected static function factory(): PersistentObjectFactory - { - return GenericDocumentFactory::new(); - } } diff --git a/tests/Integration/DataProvider/DataProviderWithProxyPersistentEntityFactoryTest.php b/tests/Integration/DataProvider/DataProviderWithProxyPersistentEntityFactoryTest.php new file mode 100644 index 000000000..68d91b7ad --- /dev/null +++ b/tests/Integration/DataProvider/DataProviderWithProxyPersistentEntityFactoryTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Integration\DataProvider; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\Test; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\Proxy; +use Zenstruck\Foundry\Persistence\ProxyGenerator; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; +use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericProxyEntityFactory; +use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; +use Zenstruck\Foundry\Tests\Integration\RequiresORM; + +/** + * @author Nicolas PHILIPPE + * @requires PHPUnit >=11.4 + */ +#[RequiresPhpunit('>=11.4')] +#[RequiresPhpunitExtension(FoundryExtension::class)] +#[IgnoreDeprecations] +final class DataProviderWithProxyPersistentEntityFactoryTest extends DataProviderWithPersistentFactoryTestCase +{ + use RequiresORM; + + #[Test] + #[DataProvider('createOneProxyObjectInDataProvider')] + public function assert_provided_data_is_proxy(?GenericModel $providedData): void + { + static::factory()::assert()->count(1); + + self::assertInstanceOf(Proxy::class, $providedData); + self::assertNotInstanceOf(Proxy::class, ProxyGenerator::unwrap($providedData)); // asserts two proxies are not nested + + static::factory()::assert()->count(1); + $providedData->_assertPersisted(); + } + + public static function createOneProxyObjectInDataProvider(): iterable + { + yield [ + static::factory()::createOne(), + ]; + } + + #[Test] + #[DataProvider('throwsExceptionWhenCreatingObjectInDataProvider')] + #[RequiresPhp('<8.4')] + public function it_throws_when_creating_persisted_object_with_non_proxy_factory_in_data_provider_without_php_84(?\Throwable $e): void + { + self::assertInstanceOf(\LogicException::class, $e); + self::assertStringStartsWith( + 'Cannot create object in a data provider for non-proxy factories.', + $e->getMessage() + ); + } + + public static function throwsExceptionWhenCreatingObjectInDataProvider(): iterable + { + try { + GenericEntityFactory::createOne(); + } catch (\Throwable $e) { + } + + yield [$e ?? null]; + } + + protected static function factory(): GenericProxyEntityFactory + { + return GenericProxyEntityFactory::new(); + } +} diff --git a/tests/Integration/DataProvider/GenericEntityFactoryTest.php b/tests/Integration/DataProvider/GenericEntityFactoryTest.php deleted file mode 100644 index aa3412575..000000000 --- a/tests/Integration/DataProvider/GenericEntityFactoryTest.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Zenstruck\Foundry\Tests\Integration\DataProvider; - -use PHPUnit\Framework\Attributes\IgnoreDeprecations; -use PHPUnit\Framework\Attributes\RequiresPhpunit; -use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; -use Zenstruck\Foundry\Persistence\PersistentObjectFactory; -use Zenstruck\Foundry\PHPUnit\FoundryExtension; -use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory; -use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericProxyEntityFactory; -use Zenstruck\Foundry\Tests\Integration\RequiresORM; - -/** - * @author Nicolas PHILIPPE - * @requires PHPUnit >=11.4 - */ -#[RequiresPhpunit('>=11.4')] -#[RequiresPhpunitExtension(FoundryExtension::class)] -#[IgnoreDeprecations] -final class GenericEntityFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase -{ - use RequiresORM; - - protected static function proxyFactory(): GenericProxyEntityFactory - { - return GenericProxyEntityFactory::new(); - } - - protected static function factory(): PersistentObjectFactory - { - return GenericEntityFactory::new(); - } -}