Skip to content

Handle test traits in DataProviderAnnotationToAttributeRector - #751

Merged
TomasVotruba merged 2 commits into
mainfrom
test-trait-data-provider
Jul 31, 2026
Merged

Handle test traits in DataProviderAnnotationToAttributeRector#751
TomasVotruba merged 2 commits into
mainfrom
test-trait-data-provider

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

@dataProvider annotations inside test traits were never converted, because TestsNodeAnalyzer::isInTestClass() only checks $classReflection->is(TestCase::class). A trait has no parent, so that check can never match — the whole file is skipped.

Real-world case in Mautic:

namespace Mautic\CoreBundle\Tests\Twig;

trait TwigIntegrationTestTrait
{
    public static function integrationTestDataProvider(): iterable
    {
        return static::getIntegrationTestData();
    }

    /**
     * @dataProvider integrationTestDataProvider
     */
    public function testIntegration($file, $message): void
    {
        // ...
    }
}

Left untouched by the PHPUnit 10 set, so the upgrade silently misses it.

Change

Traits are now detected by a Test/Tests namespace part, since test traits live next to the test cases that use them:

+        // traits have no parent, so the test case check below can never match them;
+        // fall back to the namespace, as test traits live next to the test cases that use them
+        if ($classReflection->isTrait()) {
+            return $this->isInTestsNamespace($classReflection);
+        }
+
         return array_any(
             PHPUnitClassName::TEST_CLASSES,
             fn (string $testCaseObjectClass): bool => $classReflection->is($testCaseObjectClass)
         );

DataProviderAnnotationToAttributeRector also carried its own ! $classReflection->isClass() guard, which blocked traits even once the analyzer accepted them. Dropped — isInTestClass() already rejects interfaces and enums, so the guard was redundant.

Result:

 trait SomeTestTrait
 {
-    /**
-     * @dataProvider someMethod()
-     */
+    #[\PHPUnit\Framework\Attributes\DataProvider('someMethod')]
     public function test(): void
     {
     }
 }

Note

isInTestClass() is used by ~74 rules, so this widens all of them to test traits — which is the intended behaviour, but worth a second look.

Traits have no parent class, so isInTestClass() could never match them
and every @dataProvider annotation in a test trait was left behind.

Detect traits by a Test/Tests namespace part instead, and drop the
now-redundant isClass() guard in the rule.
A trait method can only be a test method when it is public and non-static.
Beyond that, either the trait sits in a Test/Tests namespace or the method
carries the "test" prefix.
@TomasVotruba
TomasVotruba merged commit 6b8001a into main Jul 31, 2026
7 checks passed
@TomasVotruba
TomasVotruba deleted the test-trait-data-provider branch July 31, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant