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/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.9.md b/UPGRADE-2.9.md new file mode 100644 index 000000000..94fdae932 --- /dev/null +++ b/UPGRADE-2.9.md @@ -0,0 +1,53 @@ +# Migration guide from Foundry 2.8 to 2.9 + +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. + +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_9]) +; +``` diff --git a/docs/index.rst b/docs/index.rst index 9897437f4..acc60d6fd 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.9 - public function test_1(): void + The ability to globally enable Foundry with PHPUnit extension was introduced in Foundry 2.9 and requires at least + PHPUnit 10. + +.. note:: + + 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/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 4b2706232..843cddece 100644 --- a/phpunit-deprecation-baseline.xml +++ b/phpunit-deprecation-baseline.xml @@ -13,7 +13,9 @@ 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 b21b6836a..76dab41a0 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -21,6 +21,7 @@ use Zenstruck\Foundry\InMemory\InMemoryRepositoryRegistry; use Zenstruck\Foundry\Persistence\PersistedObjectsTracker; use Zenstruck\Foundry\Persistence\PersistenceManager; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; /** * @author Kevin Bond @@ -129,7 +130,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; } @@ -143,6 +146,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.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.'); + } } /** @param \Closure():self|self $configuration */ diff --git a/src/Exception/FoundryNotBooted.php b/src/Exception/FoundryNotBooted.php index 5f7fbef27..353058bae 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::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.'; + + parent::__construct($message); } } 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/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php deleted file mode 100644 index 8f58ca6bb..000000000 --- a/src/PHPUnit/BootFoundryOnDataProviderMethodCalled.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * 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; -use Zenstruck\Foundry\InMemory\AsInMemoryTest; - -/** - * @internal - * @author Nicolas PHILIPPE - */ -final class BootFoundryOnDataProviderMethodCalled implements Event\Test\DataProviderMethodCalledSubscriber -{ - public function notify(Event\Test\DataProviderMethodCalled $event): void - { - if (\method_exists($event->testMethod()->className(), '_bootForDataProvider')) { - $event->testMethod()->className()::_bootForDataProvider(); - } - - $testMethod = $event->testMethod(); - - if (AsInMemoryTest::shouldEnableInMemory($testMethod->className(), $testMethod->methodName())) { - Configuration::instance()->enableInMemory(); - } - } -} diff --git a/src/PHPUnit/BootFoundryOnPreparationStarted.php b/src/PHPUnit/BootFoundryOnPreparationStarted.php new file mode 100644 index 000000000..f22316d7c --- /dev/null +++ b/src/PHPUnit/BootFoundryOnPreparationStarted.php @@ -0,0 +1,64 @@ + + * + * 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 + { + $test = $event->test(); + + if (!$test->isTestMethod()) { + return; + } + /** @var Event\Code\TestMethod $test */ + $this->bootFoundry($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/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php index ff3ea9eb4..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,8 +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)); } - FactoriesTraitNotUsed::throwIfClassDoesNotHaveFactoriesTrait($test->className()); - foreach ($withStoryAttributes as $withStoryAttribute) { $withStoryAttribute->newInstance()->story::load(); } diff --git a/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php new file mode 100644 index 000000000..da7677ac5 --- /dev/null +++ b/src/PHPUnit/DataProvider/BootFoundryOnDataProviderMethodCalled.php @@ -0,0 +1,66 @@ + + * + * 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 PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\InMemory\AsInMemoryTest; +use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; +use Zenstruck\Foundry\Test\UnitTestConfig; + +/** + * @internal + * @author Nicolas PHILIPPE + */ +final class BootFoundryOnDataProviderMethodCalled implements Event\Test\DataProviderMethodCalledSubscriber +{ + public function notify(Event\Test\DataProviderMethodCalled $event): void + { + $this->bootFoundryForDataProvider($event->testMethod()->className()); + + $testMethod = $event->testMethod(); + + if (AsInMemoryTest::shouldEnableInMemory($testMethod->className(), $testMethod->methodName())) { + 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/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php new file mode 100644 index 000000000..774448b16 --- /dev/null +++ b/src/PHPUnit/DataProvider/ShutdownFoundryOnDataProviderMethodFinished.php @@ -0,0 +1,39 @@ + + * + * 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\Configuration; +use Zenstruck\Foundry\Persistence\PersistentObjectFromDataProviderRegistry; +use Zenstruck\Foundry\PHPUnit\KernelTestCaseHelper; + +/** + * @internal + * @author Nicolas PHILIPPE + */ +final class ShutdownFoundryOnDataProviderMethodFinished implements Event\Test\DataProviderMethodFinishedSubscriber +{ + 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/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php new file mode 100644 index 000000000..81e675725 --- /dev/null +++ b/src/PHPUnit/DataProvider/TriggerDataProviderPersistenceOnTestPrepared.php @@ -0,0 +1,43 @@ + + * + * 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 240e8c4be..7725f3673 100644 --- a/src/PHPUnit/FoundryExtension.php +++ b/src/PHPUnit/FoundryExtension.php @@ -17,35 +17,70 @@ 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 * @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(); + $subscribers[] = new TriggerDataProviderPersistenceOnTestPrepared(); + } + + $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 // @phpstan-ignore return.tooWideBool + { + return false; } - $facade->registerSubscribers(...$subscribers); + public static function isEnabled(): bool // @phpstan-ignore return.tooWideBool + { + return false; + } } } diff --git a/src/PHPUnit/KernelTestCaseHelper.php b/src/PHPUnit/KernelTestCaseHelper.php new file mode 100644 index 000000000..f0485e696 --- /dev/null +++ b/src/PHPUnit/KernelTestCaseHelper.php @@ -0,0 +1,54 @@ + + * + * 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; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\DependencyInjection\Container; + +/** + * @internal + */ +final class KernelTestCaseHelper +{ + /** + * @param class-string $class + */ + public static function getContainerForTestClass(string $class): Container + { + if (!\is_subclass_of($class, KernelTestCase::class)) { + throw new \LogicException(\sprintf('Class "%s" must extend "%s".', $class, KernelTestCase::class)); + } + + return (\Closure::bind( + fn() => $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/ShutdownFoundryOnTestFinished.php similarity index 52% rename from src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php rename to src/PHPUnit/ShutdownFoundryOnTestFinished.php index b028394b3..bfa9b99fb 100644 --- a/src/PHPUnit/ShutdownFoundryOnDataProviderMethodFinished.php +++ b/src/PHPUnit/ShutdownFoundryOnTestFinished.php @@ -14,17 +14,16 @@ namespace Zenstruck\Foundry\PHPUnit; use PHPUnit\Event; +use Zenstruck\Foundry\Configuration; /** * @internal * @author Nicolas PHILIPPE */ -final class ShutdownFoundryOnDataProviderMethodFinished implements Event\Test\DataProviderMethodFinishedSubscriber +final class ShutdownFoundryOnTestFinished implements Event\Test\FinishedSubscriber { - public function notify(Event\Test\DataProviderMethodFinished $event): void + public function notify(Event\Test\Finished $event): void { - if (\method_exists($event->testMethod()->className(), '_shutdownAfterDataProvider')) { - $event->testMethod()->className()::_shutdownAfterDataProvider(); - } + 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/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..ed4fb2726 --- /dev/null +++ b/src/Persistence/PersistentObjectFromDataProviderRegistry.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +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 lazy object, + * which will trigger the object to be persisted. + * + * Otherwise, such a test would not pass: + * ```php + * #[DataProvider('provide')] + * public function testSomething(MyEntity $entity): void + * { + * MyEntityFactory::assert()->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! + * + * 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. + * + * @internal + */ +final class PersistentObjectFromDataProviderRegistry +{ + private static ?self $instance = null; + + /** @var array> */ + private array $datasets = []; + + /** @var list */ + private array $objectsBuffer = []; + + private bool $shouldReturnObjectFromBuffer = false; + + public static function instance(): self + { + return self::$instance ?? self::$instance = new self(); + } + + public function storeDatasetIfFoundryWasUsedInDataProvider(string $className, string $methodName, ClassMethod ...$calledMethods): void + { + 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 + + if (!\is_array($dataProviderResult)) { + $dataProviderResult = \iterator_to_array($dataProviderResult); + } + + $this->datasets[$testCaseContext] = [...$this->datasets[$testCaseContext], ...$dataProviderResult]; + } + + $this->shouldReturnObjectFromBuffer = false; + + 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."); + } + } + + /** + * @template T of object + * + * @param PersistentObjectFactory $factory + * + * @return ($factory is PersistentProxyObjectFactory ? T&Proxy : T) + */ + public function deferObjectCreation(PersistentObjectFactory $factory): object + { + 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 + } + + public function triggerPersistenceForDataset(string $className, string $methodName, int|string $dataSetName): void + { + $testCaseContext = $this->testCaseContext($className, $methodName); + + if (!isset($this->datasets[$testCaseContext][$dataSetName])) { + return; + } + + initialize_lazy_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/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 8bd39cf89..6b5f80291 100644 --- a/src/Test/Factories.php +++ b/src/Test/Factories.php @@ -15,8 +15,9 @@ 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; +use function Zenstruck\Foundry\Persistence\initialize_lazy_object; /** * @author Kevin Bond @@ -27,9 +28,15 @@ trait Factories * @internal * @before */ - #[Before] + #[Before(5)] public function _beforeHook(): void { + if (FoundryExtension::isEnabled()) { + 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; + } + $this->_bootFoundry(); $this->_loadDataProvidedProxies(); } @@ -38,47 +45,13 @@ public function _beforeHook(): void * @internal * @after */ - #[After] + #[After(5)] 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()) { 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 - }); - } - - /** - * @internal - * @see \Zenstruck\Foundry\PHPUnit\ShutdownFoundryOnDataProviderMethodFinished - */ - public static function _shutdownAfterDataProvider(): 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 - } Configuration::shutdown(); } @@ -132,8 +105,10 @@ 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(); // @phpstan-ignore method.internal - initialize_proxy_object($providedData); + initialize_lazy_object($providedData); } } 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/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/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 54% rename from tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php rename to tests/Integration/DataProvider/DataProviderWithPersistentEntityFactoryTest.php index e0c918556..74a732e77 100644 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryAndPHP84InKernelTest.php +++ b/tests/Integration/DataProvider/DataProviderWithPersistentEntityFactoryTest.php @@ -20,11 +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\Factories; 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 @@ -34,10 +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 Factories; - use ResetDatabase; + use RequiresORM; #[Test] #[DataProvider('createOneObjectInDataProvider')] @@ -47,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 @@ -55,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/DataProviderWithPersistentFactoryInKernelTestCase.php deleted file mode 100644 index 1a6f07194..000000000 --- a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryInKernelTestCase.php +++ /dev/null @@ -1,189 +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\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 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\Factories; -use Zenstruck\Foundry\Test\ResetDatabase; -use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; -use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel; - -/** - * @author Nicolas PHILIPPE - * @requires PHPUnit >=11.4 - */ -#[RequiresPhpunit('>=11.4')] -#[RequiresPhpunitExtension(FoundryExtension::class)] -#[IgnoreDeprecations] -abstract class DataProviderWithPersistentFactoryInKernelTestCase extends KernelTestCase -{ - use Factories; - use ResetDatabase; - - #[Test] - #[DataProvider('createOneProxyObjectInDataProvider')] - public function assert_it_can_create_one_object_in_data_provider(?GenericModel $providedData): void - { - static::proxyFactory()::assert()->count(1); - - 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()); - } - - public static function createOneProxyObjectInDataProvider(): iterable - { - yield 'createOne()' => [ - static::proxyFactory()::createOne(['prop1' => 'value set in data provider']), - ]; - - yield 'create()' => [ - static::proxyFactory()->create(['prop1' => 'value set in data provider']), - ]; - } - - #[Test] - #[DataProvider('createMultipleObjectsInDataProvider')] - public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void - { - self::assertIsArray($providedData); - static::proxyFactory()::assert()->count(2); - self::assertSame('prop 1', $providedData[0]->getProp1()); - self::assertSame('prop 2', $providedData[1]->getProp1()); - } - - public static function createMultipleObjectsInDataProvider(): iterable - { - yield 'createSequence()' => [ - static::proxyFactory()::createSequence([ - ['prop1' => 'prop 1'], - ['prop1' => 'prop 2'], - ]), - ]; - - yield 'FactoryCollection::create()' => [ - static::proxyFactory()->sequence([ - ['prop1' => 'prop 1'], - ['prop1' => 'prop 2'], - ])->create(), - ]; - } - - #[Test] - #[DataProvider('useGetterOnProxyObjectCreatedInDataProvider')] - public function assert_using_getter_proxy_object_created_in_a_data_provider_throws(?\Throwable $e): void - { - self::assertInstanceOf(\LogicException::class, $e); - self::assertStringStartsWith('Cannot access to a persisted object from a data provider.', $e->getMessage()); - } - - public static function useGetterOnProxyObjectCreatedInDataProvider(): iterable - { - try { - static::proxyFactory()::createOne()->getProp1(); - } catch (\Throwable $e) { - } - - yield [$e ?? null]; - } - - #[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 { - static::factory()::createOne(); - } catch (\Throwable $e) { - } - - yield [$e ?? null]; - } - - #[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 - { - static::proxyFactory()::assert()->count(1); - - self::assertInstanceOf(GenericModel::class, $providedData); - self::assertSame('value set in data provider', $providedData->getProp1()); - } - - public static function createOneObjectInDataProvider(): iterable - { - yield 'createOne()' => [ - static::proxyFactory()::createOne(['prop1' => 'value set in data provider']), - ]; - - yield 'create()' => [ - static::proxyFactory()->create(['prop1' => 'value set in data provider']), - ]; - } - - #[Test] - #[DataProvider('useGetterOnObjectCreatedInDataProvider')] - public function assert_it_can_use_getter_on_non_persisted_object_created_in_data_provider( - string $providedData, - mixed $expectedData, - ): void { - self::assertEquals($expectedData, ProxyGenerator::unwrap($providedData)); - } - - 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 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(), - 'default1', - ]; - } - - /** - * @return PersistentProxyObjectFactory - */ - abstract protected static function proxyFactory(): PersistentProxyObjectFactory; - - /** - * @return PersistentObjectFactory - */ - abstract protected static function factory(): PersistentObjectFactory; -} diff --git a/tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php new file mode 100644 index 000000000..f12820601 --- /dev/null +++ b/tests/Integration/DataProvider/DataProviderWithPersistentFactoryTestCase.php @@ -0,0 +1,198 @@ +count(1); + + self::assertNotNull($providedData); + self::assertSame('value set in data provider', $providedData->getProp1()); + + assert_persisted($providedData); + } + + public static function createOneObjectInDataProvider(): iterable + { + yield 'createOne()' => [ + static::factory()::createOne(['prop1' => 'value set in data provider']), + ]; + + yield 'create()' => [ + static::factory()->create(['prop1' => 'value set in data provider']), + ]; + } + + #[Test] + #[DataProvider('createMultipleObjectsInDataProvider')] + public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void + { + self::assertIsArray($providedData); + static::factory()::assert()->count(2); + + self::assertSame('prop 1', $providedData[0]->getProp1()); + self::assertSame('prop 2', $providedData[1]->getProp1()); + } + + public static function createMultipleObjectsInDataProvider(): iterable + { + yield 'createSequence()' => [ + static::factory()::createSequence([ + ['prop1' => 'prop 1'], + ['prop1' => 'prop 2'], + ]), + ]; + + 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('dataProviderRetuningArray')] + public function assert_it_can_use_data_provider_returning_array(?GenericModel $providedData): void + { + static::factory()::assert()->count(1); + + self::assertNotNull($providedData); + self::assertSame('value set in data provider', $providedData->getProp1()); + } + + public static function dataProviderRetuningArray(): array + { + 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()); + } + } + + 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('dataProviderUsingRandomRanges')] + public function assert_data_provider_throws_when_using_range_in_data_provider(?\Throwable $throwable): void + { + self::assertInstanceOf(\InvalidArgumentException::class, $throwable); + self::assertSame('Using randomized "range" factory in data provider is not supported.', $throwable->getMessage()); + } + + public static function dataProviderUsingRandomRanges(): iterable + { + try { + GenericEntityFactory::createRange(1, 2); + } catch (\Throwable $e) { + } + + yield [$e ?? null]; + + try { + GenericEntityFactory::new()->range(1, 2); + } catch (\Throwable $e) { + } + + yield [$e ?? null]; + } + + #[Test] + #[DataProvider('useGetterOnProxyObjectCreatedInDataProvider')] + public function assert_using_getter_proxy_object_created_in_a_data_provider_throws(?\Throwable $e): void + { + self::assertInstanceOf(\LogicException::class, $e); + self::assertStringStartsWith('Cannot access to a persisted object from a data provider.', $e->getMessage()); + } + + public static function useGetterOnProxyObjectCreatedInDataProvider(): iterable + { + try { + static::factory()::createOne()->getProp1(); + } catch (\Throwable $e) { + } + + yield [$e ?? null]; + } + + #[Test] + #[DataProvider('useGetterOnObjectCreatedInDataProvider')] + public function assert_it_can_use_getter_on_non_persisted_object_created_in_data_provider( + string $providedData, + mixed $expectedData, + ): void { + self::assertEquals($expectedData, ProxyGenerator::unwrap($providedData)); + } + + public static function useGetterOnObjectCreatedInDataProvider(): iterable + { + yield 'object factory' => [Object1Factory::createOne()->getProp1(), 'router-constructor']; + yield 'persistent factory' => [static::factory()->withoutPersisting()->create()->getProp1(), 'default1']; + yield 'persistent factory using many' => [ + static::factory()->withoutPersisting()->many(1)->create()[0]->getProp1(), + 'default1', + ]; + } + + /** + * @return PersistentObjectFactory + */ + abstract protected static function factory(): PersistentObjectFactory; +} diff --git a/tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php b/tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php similarity index 78% rename from tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php rename to tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php index f18c5136b..138d66d0a 100644 --- a/tests/Integration/DataProvider/GenericDocumentProxyFactoryTest.php +++ b/tests/Integration/DataProvider/DataProviderWithProxyPersistentDocumentFactoryTest.php @@ -27,17 +27,12 @@ #[RequiresPhpunit('>=11.4')] #[RequiresPhpunitExtension(FoundryExtension::class)] #[IgnoreDeprecations] -final class GenericDocumentProxyFactoryTest 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/GenericEntityProxyFactoryTest.php b/tests/Integration/DataProvider/GenericEntityProxyFactoryTest.php deleted file mode 100644 index 1f5eadb08..000000000 --- a/tests/Integration/DataProvider/GenericEntityProxyFactoryTest.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 GenericEntityProxyFactoryTest extends DataProviderWithPersistentFactoryInKernelTestCase -{ - use RequiresORM; - - protected static function proxyFactory(): GenericProxyEntityFactory - { - return GenericProxyEntityFactory::new(); - } - - protected static function factory(): PersistentObjectFactory - { - return GenericEntityFactory::new(); - } -} 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/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php similarity index 90% rename from tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php rename to tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php index 889784d62..6681111fd 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait.php +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest.php @@ -18,8 +18,9 @@ use Zenstruck\Foundry\Test\ResetDatabase; #[RequiresPhpunit('>=11.0')] -final class KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTrait extends KernelTestCase +final class KernelTestCaseWithOnlyResetDatabaseTraitTestWithoutFactoriesTest extends KernelTestCase { use KernelTestCaseWithoutFactoriesTrait; use ResetDatabase; + use SkipWithPHPUnitExtension; } diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTrait.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php similarity index 77% rename from tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTrait.php rename to tests/Integration/ForceFactoriesTraitUsage/KernelTestWithoutFactoriesTest.php index 38669bd1c..ca9bf444d 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; + use KernelTestCaseWithoutFactoriesTrait, SkipWithPHPUnitExtension; } diff --git a/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php new file mode 100644 index 000000000..f489d99cf --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/SkipWithPHPUnitExtension.php @@ -0,0 +1,30 @@ + + * + * 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; +use PHPUnit\Framework\TestCase; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; + +/** + * @phpstan-require-extends TestCase + */ +trait SkipWithPHPUnitExtension +{ + #[Before] + public function _skipWithPHPUnitExtension(): void + { + if (FoundryExtension::isEnabled()) { + self::markTestSkipped('This test requires *NOT* using Foundry\'s PHUnit extension.'); + } + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php index b1d2b59f9..7b416194a 100644 --- a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php +++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php @@ -22,7 +22,7 @@ #[RequiresPhpunit('>=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(); + } +} 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-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.9.php b/utils/rector/config/foundry-2.9.php new file mode 100644 index 000000000..99626b9db --- /dev/null +++ b/utils/rector/config/foundry-2.9.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..aceccf7a2 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_9 = __DIR__.'/../config/foundry-2.9.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'; } }