From dcd6ef857f5449dd7d2af8cf414f1a2568bb0ffe Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Wed, 18 Dec 2024 18:38:12 +0100 Subject: [PATCH] feat: throw an error if Factories trait is not used in a KernelTestCase --- .gitignore | 2 +- .php-cs-fixer.dist.php | 7 ++ phpunit.xml.dist | 1 + src/Configuration.php | 3 + src/Exception/FactoriesTraitNotUsed.php | 67 +++++++++++++++++++ src/PHPUnit/BuildStoryOnTestPrepared.php | 3 + ...TestCaseWithBothTraitsInWrongOrderTest.php | 45 +++++++++++++ .../KernelTestCaseWithBothTraitsTest.php | 45 +++++++++++++ .../KernelTestCaseWithFactoriesTraitTest.php | 44 ++++++++++++ ...TestCaseWithOnlyResetDatabaseTraitTest.php | 50 ++++++++++++++ ...ernelTestCaseWithoutFactoriesTraitTest.php | 52 ++++++++++++++ .../UnitTestCaseWithFactoriesTraitTest.php | 34 ++++++++++ .../UnitTestCaseWithoutFactoriesTraitTest.php | 32 +++++++++ tests/Integration/Maker/MakerTestCase.php | 3 + 14 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 src/Exception/FactoriesTraitNotUsed.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php create mode 100644 tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php diff --git a/.gitignore b/.gitignore index 6d5aba94..58efde94 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 5fca00a6..325e8db3 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 c6b9f26c..d6204a00 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,6 +20,7 @@ tests tests/Integration/Migration/ResetDatabaseWithMigrationTest.php + tests/Integration/ForceFactoriesTraitUsage tests/Integration/Migration/ResetDatabaseWithMigrationTest.php diff --git a/src/Configuration.php b/src/Configuration.php index 048604b4..8e0dc219 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 00000000..a534f037 --- /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 8689dbfe..ff3ea9eb 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 00000000..f36dec86 --- /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 00000000..fd6de3f9 --- /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 00000000..80d40751 --- /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 00000000..a5319d6d --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php @@ -0,0 +1,50 @@ + + * + * 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\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Test\ResetDatabase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithOnlyResetDatabaseTraitTest extends KernelTestCase +{ + use ResetDatabase; + + #[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_should_throw(): void + { + $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/'); + + Object1Factory::createOne(); + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php new file mode 100644 index 00000000..83b36f35 --- /dev/null +++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php @@ -0,0 +1,52 @@ + + * + * 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\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory; + +#[RequiresPhpunit('>=11.0')] +final class KernelTestCaseWithoutFactoriesTraitTest extends KernelTestCase +{ + #[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] + public function booting_kernel_without_using_foundry_and_without_factories_trait_should_not_throw(): void + { + self::getContainer()->get('.zenstruck_foundry.configuration'); + + $this->expectNotToPerformAssertions(); + } +} diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php new file mode 100644 index 00000000..b1d2b59f --- /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 00000000..2275b1af --- /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(); + } +} diff --git a/tests/Integration/Maker/MakerTestCase.php b/tests/Integration/Maker/MakerTestCase.php index 1e830d3d..2984dd64 100644 --- a/tests/Integration/Maker/MakerTestCase.php +++ b/tests/Integration/Maker/MakerTestCase.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\String\Slugger\AsciiSlugger; +use Zenstruck\Foundry\Test\Factories; /** * @author Kevin Bond @@ -21,6 +22,8 @@ */ abstract class MakerTestCase extends KernelTestCase { + use Factories; + /** * @before */