diff --git a/CHANGELOG b/CHANGELOG index 1ca2712c6..d2ce81567 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.27.2 (2026-XX-XX) + * Make the `IntegrationTestCase` and `NodeTestCase` test helpers compatible with PHPUnit 11 * Skip the sandbox `__toString` check on arguments whose PHP parameter type cannot implicitly coerce to string # 3.27.1 (2026-05-30) diff --git a/src/Test/IntegrationTestCase.php b/src/Test/IntegrationTestCase.php index d995c6a03..c35ba8fe4 100644 --- a/src/Test/IntegrationTestCase.php +++ b/src/Test/IntegrationTestCase.php @@ -11,6 +11,8 @@ namespace Twig\Test; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Twig\Environment; use Twig\Error\Error; @@ -118,10 +120,13 @@ abstract class IntegrationTestCase extends TestCase } /** + * The annotation feeds PHPUnit < 10; the attribute feeds PHPUnit >= 10 and must point to a static provider, as PHPUnit >= 11 rejects non-static ones. + * * @dataProvider getTests * * @return void */ + #[DataProvider('provideTests')] public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') { $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); @@ -134,11 +139,22 @@ abstract class IntegrationTestCase extends TestCase * * @return void */ + #[DataProvider('provideLegacyTests'), Group('legacy')] public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') { $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); } + final public static function provideTests(): iterable + { + return self::assembleTests(false, static::getFixturesDirectory()); + } + + final public static function provideLegacyTests(): iterable + { + return self::assembleTests(true, static::getFixturesDirectory()); + } + /** * @return iterable * @@ -153,6 +169,11 @@ abstract class IntegrationTestCase extends TestCase $fixturesDir = $this->getFixturesDir(); } + return self::assembleTests($legacyTests, $fixturesDir); + } + + private static function assembleTests(bool $legacyTests, string $fixturesDir): array + { $fixturesDir = realpath($fixturesDir); $tests = []; diff --git a/src/Test/NodeTestCase.php b/src/Test/NodeTestCase.php index 0cb5b2fab..3ce58b3bf 100644 --- a/src/Test/NodeTestCase.php +++ b/src/Test/NodeTestCase.php @@ -45,12 +45,14 @@ abstract class NodeTestCase extends TestCase } /** + * The non-static "getTests" is intentionally not mirrored as an attribute: PHPUnit >= 11 rejects non-static providers, so PHPUnit >= 10 relies on the static "provideTests" instead. + * * @dataProvider getTests * @dataProvider provideTests * * @return void */ - #[DataProvider('getTests'), DataProvider('provideTests')] + #[DataProvider('provideTests')] public function testCompile($node, $source, $environment = null, $isPattern = false) { $this->assertNodeCompilation($source, $node, $environment, $isPattern); diff --git a/tests/Test/DataProviderCompatibilityTest.php b/tests/Test/DataProviderCompatibilityTest.php new file mode 100644 index 000000000..515c86efb --- /dev/null +++ b/tests/Test/DataProviderCompatibilityTest.php @@ -0,0 +1,57 @@ += 11, + * which rejects non-static data providers. + */ +class DataProviderCompatibilityTest extends TestCase +{ + /** + * @return iterable + */ + public static function provideTestCaseClasses(): iterable + { + yield [IntegrationTestCase::class]; + yield [NodeTestCase::class]; + } + + /** + * @dataProvider provideTestCaseClasses + * + * @param class-string $class + */ + #[DataProvider('provideTestCaseClasses')] + public function testDataProviderAttributesReferenceStaticMethods(string $class): void + { + // Read attribute metadata via reflection without instantiating the attribute, + // so the check also runs under PHPUnit < 10 where the attribute class is absent. + $found = false; + foreach ((new \ReflectionClass($class))->getMethods() as $method) { + foreach ($method->getAttributes(DataProvider::class) as $attribute) { + $found = true; + $provider = $attribute->getArguments()[0]; + $r = new \ReflectionMethod($class, $provider); + $this->assertTrue($r->isStatic(), \sprintf('Data provider "%s::%s()" referenced by "%s()" must be static for PHPUnit >= 11.', $class, $provider, $method->getName())); + $this->assertSame(0, $r->getNumberOfRequiredParameters(), \sprintf('Data provider "%s::%s()" must not require arguments.', $class, $provider)); + } + } + + $this->assertTrue($found, \sprintf('"%s" should declare at least one "#[DataProvider]" attribute.', $class)); + } +}