diff --git a/.gitignore b/.gitignore index 5ce100385..2a766832b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /.phpunit.cache /vendor/ /bin/tools/*/vendor/ -/bin/tools/php-cs-fixer/composer.lock +/bin/tools/csfixer /build/ /.php-cs-fixer.cache /.phpunit.result.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5fca00a68..325e8db30 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -7,6 +7,13 @@ \file_get_contents('https://raw.githubusercontent.com/zenstruck/.github/main/.php-cs-fixer.dist.php') ); +$finder = PhpCsFixer\Finder::create() + ->in([__DIR__.'/src', __DIR__.'/tests']) + + // we want to keep the traits in "wrong order" + ->notName('KernelTestCaseWithBothTraitsInWrongOrderTest.php') +; + try { return require $file; } finally { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 95f1c3e92..15de54d9a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,6 +20,7 @@ tests tests/Integration/ResetDatabase + tests/Integration/ForceFactoriesTraitUsage tests/Integration/ResetDatabase diff --git a/src/Configuration.php b/src/Configuration.php index 048604b4c..8e0dc219d 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -12,6 +12,7 @@ namespace Zenstruck\Foundry; use Faker; +use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed; use Zenstruck\Foundry\Exception\FoundryNotBooted; use Zenstruck\Foundry\Exception\PersistenceDisabled; use Zenstruck\Foundry\Exception\PersistenceNotAvailable; @@ -90,6 +91,8 @@ public static function instance(): self throw new FoundryNotBooted(); } + FactoriesTraitNotUsed::throwIfComingFromKernelTestCaseWithoutFactoriesTrait(); + return \is_callable(self::$instance) ? (self::$instance)() : self::$instance; } diff --git a/src/Exception/FactoriesTraitNotUsed.php b/src/Exception/FactoriesTraitNotUsed.php new file mode 100644 index 000000000..9d5015c58 --- /dev/null +++ b/src/Exception/FactoriesTraitNotUsed.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Exception; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Test\Factories; + +/** + * @author Nicolas PHILIPPE + */ +final class FactoriesTraitNotUsed extends \LogicException +{ + /** + * @param class-string $class + */ + private function __construct(string $class) + { + parent::__construct( + \sprintf('You must use the trait "%s" in "%s" in order to use Foundry.', Factories::class, $class) + ); + } + + public static function throwIfComingFromKernelTestCaseWithoutFactoriesTrait(): void + { + $backTrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); // @phpstan-ignore ekinoBannedCode.function + + foreach ($backTrace as $trace) { + if ( + '->' === ($trace['type'] ?? null) + && isset($trace['class']) + && KernelTestCase::class !== $trace['class'] + && \is_a($trace['class'], KernelTestCase::class, allow_string: true) + && !(new \ReflectionClass($trace['class']))->hasMethod('_bootFoundry') + ) { + self::throwIfClassDoesNotHaveFactoriesTrait($trace['class']); + } + } + } + + /** + * @param class-string $class + */ + public static function throwIfClassDoesNotHaveFactoriesTrait(string $class): void + { + if (!(new \ReflectionClass($class))->hasMethod('_bootFoundry')) { + // throw new self($class); + trigger_deprecation( + 'zenstruck/foundry', + '2.4', + 'In order to use Foundry, you must use the trait "%s" in your "%s" tests. This will throw an exception in 3.0.', + KernelTestCase::class, + $class + ); + } + } +} diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php index 8689dbfee..ff3ea9eb4 100644 --- a/src/PHPUnit/BuildStoryOnTestPrepared.php +++ b/src/PHPUnit/BuildStoryOnTestPrepared.php @@ -16,6 +16,7 @@ use PHPUnit\Event; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Foundry\Attribute\WithStory; +use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed; /** * @internal @@ -46,6 +47,8 @@ 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/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php new file mode 100644 index 000000000..f36dec860 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php @@ -0,0 +1,45 @@ + + * + * 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\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithBothTraitsInWrongOrderTest extends KernelTestCase +{ + use ResetDatabase, Factories; + + #[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/KernelTestCaseWithBothTraitsTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php new file mode 100644 index 000000000..fd6de3f93 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php @@ -0,0 +1,45 @@ + + * + * 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\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithBothTraitsTest extends KernelTestCase +{ + use Factories, ResetDatabase; + + #[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/KernelTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php new file mode 100644 index 000000000..80d40751d --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.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; + +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithFactoriesTraitTest extends KernelTestCase +{ + use Factories; + + #[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/KernelTestCaseWithOnlyResetDatabaseTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php new file mode 100644 index 000000000..6727c074f --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php @@ -0,0 +1,23 @@ + + * + * 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\RequiresPhpunit; +use Zenstruck\Foundry\Test\ResetDatabase; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithOnlyResetDatabaseTraitTest extends KernelTestCaseWithoutFactoriesTraitTestCase +{ + use ResetDatabase; +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php new file mode 100644 index 000000000..dae664edc --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php @@ -0,0 +1,21 @@ + + * + * 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\RequiresPhpunit; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithoutFactoriesTraitTest extends KernelTestCaseWithoutFactoriesTraitTestCase +{ +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php new file mode 100644 index 000000000..ba73e8a73 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php @@ -0,0 +1,91 @@ + + * + * 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\After; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Configuration; +use Zenstruck\Foundry\PHPUnit\FoundryExtension; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; +use Zenstruck\Foundry\Tests\Fixture\Stories\ObjectStory; + +abstract class KernelTestCaseWithoutFactoriesTraitTestCase extends KernelTestCase +{ + #[Test] + public function not_using_foundry_should_not_throw(): void + { + $this->expectNotToPerformAssertions(); + } + + #[Test] + public function not_using_foundry_should_not_throw_even_when_container_is_used(): void + { + self::getContainer()->get('.zenstruck_foundry.configuration'); + + $this->expectNotToPerformAssertions(); + } + + #[Test] + #[IgnoreDeprecations] + public function using_foundry_without_trait_should_throw(): void + { + $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/'); + + Object1Factory::createOne(); + } + + #[Test] + #[IgnoreDeprecations] + public function using_foundry_without_trait_should_throw_even_when_kernel_is_booted(): void + { + $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/'); + + self::getContainer()->get('.zenstruck_foundry.configuration'); + + Object1Factory::createOne(); + } + + #[Test] + #[RequiresPhpunitExtension(FoundryExtension::class)] + #[IgnoreDeprecations] + public function using_a_story_without_factories_trait_should_throw(): void + { + $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/'); + + ObjectStory::load(); + } + + /** + * We need to at least boot and shutdown Foundry to avoid unpredictable behaviors. + * + * In user land, Foundry can work without the trait, because it may have been booted in a previous test. + */ + #[Before] + public function _beforeHook(): void + { + Configuration::boot(static function(): Configuration { + return static::getContainer()->get('.zenstruck_foundry.configuration'); // @phpstan-ignore return.type + }); + } + + #[After] + public static function _shutdownFoundry(): void + { + Configuration::shutdown(); + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php new file mode 100644 index 000000000..b1d2b59f9 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.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; + +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class UnitTestCaseWithFactoriesTraitTest extends TestCase +{ + use Factories; + + #[Test] + public function should_not_throw(): void + { + Object1Factory::createOne(); + + $this->expectNotToPerformAssertions(); + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php new file mode 100644 index 000000000..2275b1af3 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php @@ -0,0 +1,32 @@ + + * + * 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\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Zenstruck\Foundry\Exception\FoundryNotBooted; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class UnitTestCaseWithoutFactoriesTraitTest extends TestCase +{ + #[Test] + public function should_throw(): void + { + $this->expectException(FoundryNotBooted::class); + + Object1Factory::createOne(); + } +}