From e588070a92dc5658ae4b2d87cd2931c2bd23dec1 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 12:36:41 +0100 Subject: [PATCH 1/7] [phpunit 12] Add AllowMockObjectsWithoutExpectationsAttributeRector --- config/sets/phpunit120.php | 5 + ...WithoutExpectationsAttributeRectorTest.php | 28 +++ .../Fixture/skip_only_single_test.php.inc | 19 ++ .../Fixture/some_class.php.inc | 54 ++++++ .../config/configured_rule.php | 9 + ...ectsWithoutExpectationsAttributeRector.php | 170 ++++++++++++++++++ src/Enum/PHPUnitAttribute.php | 5 + src/Enum/PHPUnitClassName.php | 2 + 8 files changed, 292 insertions(+) create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/AllowMockObjectsWithoutExpectationsAttributeRectorTest.php create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_only_single_test.php.inc create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/config/configured_rule.php create mode 100644 rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php diff --git a/config/sets/phpunit120.php b/config/sets/phpunit120.php index 611a42b8..7a8b0f88 100644 --- a/config/sets/phpunit120.php +++ b/config/sets/phpunit120.php @@ -4,6 +4,7 @@ use Rector\Config\RectorConfig; use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector; +use Rector\PHPUnit\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\AssertIsTypeMethodCallRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\RemoveOverrideFinalConstructTestCaseRector; @@ -14,5 +15,9 @@ // stubs over mocks CreateStubOverCreateMockArgRector::class, + + // experimental, from PHPUnit 12.5.2 + // @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43 + AllowMockObjectsWithoutExpectationsAttributeRector::class, ]); }; diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/AllowMockObjectsWithoutExpectationsAttributeRectorTest.php b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/AllowMockObjectsWithoutExpectationsAttributeRectorTest.php new file mode 100644 index 00000000..1d6c75c6 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/AllowMockObjectsWithoutExpectationsAttributeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_only_single_test.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_only_single_test.php.inc new file mode 100644 index 00000000..106dfbbe --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_only_single_test.php.inc @@ -0,0 +1,19 @@ +someMock = $this->createMock(\stdClass::class); + } + + public function testOne() + { + } +} diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc new file mode 100644 index 00000000..a6e639b1 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc @@ -0,0 +1,54 @@ +someMock = $this->createMock(\stdClass::class); + } + + public function testOne() + { + } + + public function testTwo() + { + + } +} + +?> +----- +someMock = $this->createMock(\stdClass::class); + } + + public function testOne() + { + } + + public function testTwo() + { + + } +} + +?> diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/config/configured_rule.php b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/config/configured_rule.php new file mode 100644 index 00000000..baae2bb5 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AllowMockObjectsWithoutExpectationsAttributeRector::class]); diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php new file mode 100644 index 00000000..7261d589 --- /dev/null +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -0,0 +1,170 @@ +testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + // attribute must exist for the rule to work + if (! $this->reflectionProvider->hasClass(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)) { + return null; + } + + // already filled + if ($this->attributeFinder->hasAttributeByClasses( + $node, + [PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS] + )) { + return null; + } + + // has mock objects properties and setUp() method? + + if (! $node->getMethod('setUp') instanceof ClassMethod) { + return null; + } + + if (! $this->hasMockObjectProperty($node)) { + return null; + } + + // @todo add the attribute if has more than 1 public test* method + $testMethodCount = 0; + + foreach ($node->getMethods() as $classMethod) { + if ($this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + ++$testMethodCount; + } + } + + if ($testMethodCount < 2) { + return null; + } + + // add attribute + $node->attrGroups[] = new AttributeGroup([ + new Attribute(new FullyQualified(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)), + ]); + + return $node; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Add #[AllowMockObjectsWithoutExpectations] attribute to PHPUnit test classes with mock properties used in multiple methods', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; +final class SomeTest extends TestCase +{ + private \PHPUnit\Framework\MockObject\MockObject $someServiceMock; + + protected function setUp(): void + { + $this->someServiceMock = $this->createMock(SomeService::class); + } + + public function testOne(): void + { + // use $this->someServiceMock + } + + public function testTwo(): void + { + // use $this->someServiceMock + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; + +#[AllowMockObjectsWithoutExpectations] +final class SomeTest extends TestCase +{ + private \PHPUnit\Framework\MockObject\MockObject $someServiceMock; + + protected function setUp(): void + { + $this->someServiceMock = $this->createMock(SomeService::class); + } + + public function testOne(): void + { + // use $this->someServiceMock + } + + public function testTwo(): void + { + // use $this->someServiceMock + } +} +CODE_SAMPLE + ), + + ] + ); + + } + + private function hasMockObjectProperty(Class_ $class): bool + { + foreach ($class->getProperties() as $property) { + if (! $property->type instanceof Name) { + continue; + } + + if ($this->isName($property->type, PHPUnitClassName::MOCK_OBJECT)) { + return true; + } + } + + return false; + } +} diff --git a/src/Enum/PHPUnitAttribute.php b/src/Enum/PHPUnitAttribute.php index bf09c0fa..a540f7f6 100644 --- a/src/Enum/PHPUnitAttribute.php +++ b/src/Enum/PHPUnitAttribute.php @@ -23,4 +23,9 @@ final class PHPUnitAttribute public const string REQUIRES_SETTING = 'PHPUnit\Framework\Attributes\RequiresSetting'; public const string TEST = 'PHPUnit\Framework\Attributes\Test'; + + /** + * @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43 + */ + public const string ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS = 'PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations'; } diff --git a/src/Enum/PHPUnitClassName.php b/src/Enum/PHPUnitClassName.php index 51a4ee21..5171edfa 100644 --- a/src/Enum/PHPUnitClassName.php +++ b/src/Enum/PHPUnitClassName.php @@ -23,6 +23,8 @@ final class PHPUnitClassName public const string TEST_LISTENER = 'PHPUnit\Framework\TestListener'; + public const string MOCK_OBJECT = 'PHPUnit\Framework\MockObject\MockObject'; + /** * @var string[] */ From a0186baac53f8129bdf86b82fdcf46235666061d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 12:56:24 +0100 Subject: [PATCH 2/7] fixup! [phpunit 12] Add AllowMockObjectsWithoutExpectationsAttributeRector --- config/sets/phpunit120.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sets/phpunit120.php b/config/sets/phpunit120.php index 7a8b0f88..cd06fef1 100644 --- a/config/sets/phpunit120.php +++ b/config/sets/phpunit120.php @@ -18,6 +18,6 @@ // experimental, from PHPUnit 12.5.2 // @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43 - AllowMockObjectsWithoutExpectationsAttributeRector::class, + // AllowMockObjectsWithoutExpectationsAttributeRector::class, ]); }; From 73e1ed6c73faa9caa716ab267a0262ebcecb30c6 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 12:58:22 +0100 Subject: [PATCH 3/7] add fixtures to handle --- ..._tests_methods_define_expectations.php.inc | 25 +++++++++++++++++++ ...if_mock_not_used_in_2_test_methods.php.inc | 24 ++++++++++++++++++ .../Fixture/some_class.php.inc | 2 +- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_all_tests_methods_define_expectations.php.inc create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_mock_not_used_in_2_test_methods.php.inc diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_all_tests_methods_define_expectations.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_all_tests_methods_define_expectations.php.inc new file mode 100644 index 00000000..da0e0722 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_all_tests_methods_define_expectations.php.inc @@ -0,0 +1,25 @@ +someMock = $this->createMock(\stdClass::class); + } + + public function testOne() + { + $this->someMock->method('doSomething')->willReturn('value'); + } + + public function testTwo() + { + $this->someMock->method('doSomethingElse')->willReturn('another value'); + } +} diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_mock_not_used_in_2_test_methods.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_mock_not_used_in_2_test_methods.php.inc new file mode 100644 index 00000000..2d61aa44 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/skip_if_mock_not_used_in_2_test_methods.php.inc @@ -0,0 +1,24 @@ +someMock = $this->createMock(\stdClass::class); + } + + public function testOne() + { + } + + public function testTwo() + { + + } +} diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc index a6e639b1..ba5ad6ec 100644 --- a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc @@ -43,11 +43,11 @@ final class SomeClass extends TestCase public function testOne() { + $this->someMock->method('doSomething')->willReturn('value'); } public function testTwo() { - } } From 936c901bd29e14a7942d8ea48fe81642590f0d98 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 12:59:36 +0100 Subject: [PATCH 4/7] wip --- .../Fixture/some_class.php.inc | 2 +- .../AllowMockObjectsWithoutExpectationsAttributeRector.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc index ba5ad6ec..18f0548d 100644 --- a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector/Fixture/some_class.php.inc @@ -15,11 +15,11 @@ final class SomeClass extends TestCase public function testOne() { + $this->someMock->method('doSomething')->willReturn('value'); } public function testTwo() { - } } diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php index 7261d589..488dd572 100644 --- a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -76,6 +76,9 @@ public function refactor(Node $node): ?Class_ foreach ($node->getMethods() as $classMethod) { if ($this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + // is a mock property used in the method? + // skip if so + ++$testMethodCount; } } From a4afc09a3f3d71598d13b746455f8c8c3e7d0b1e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 14:46:35 +0100 Subject: [PATCH 5/7] optimize --- ...ectsWithoutExpectationsAttributeRector.php | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php index 488dd572..544afa53 100644 --- a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -17,6 +17,7 @@ use Rector\PHPUnit\Enum\PHPUnitClassName; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; use Rector\Rector\AbstractRector; +use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -44,30 +45,14 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Class_ { - if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + if ($this->shouldSkipClass($node)) { return null; } - // attribute must exist for the rule to work - if (! $this->reflectionProvider->hasClass(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)) { - return null; - } + $mockObjectPropertyNames = $this->matchMockObjectPropertyNames($node); - // already filled - if ($this->attributeFinder->hasAttributeByClasses( - $node, - [PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS] - )) { - return null; - } - - // has mock objects properties and setUp() method? - - if (! $node->getMethod('setUp') instanceof ClassMethod) { - return null; - } - - if (! $this->hasMockObjectProperty($node)) { + // there are no mock object properties + if ($mockObjectPropertyNames === []) { return null; } @@ -156,18 +141,50 @@ public function testTwo(): void } - private function hasMockObjectProperty(Class_ $class): bool + /** + * @return string[] + */ + private function matchMockObjectPropertyNames(Class_ $class): array { + $propertyNames = []; + foreach ($class->getProperties() as $property) { if (! $property->type instanceof Name) { continue; } - if ($this->isName($property->type, PHPUnitClassName::MOCK_OBJECT)) { - return true; + if (! $this->isName($property->type, PHPUnitClassName::MOCK_OBJECT)) { + continue; } + + $propertyNames[] = $this->getName($property->props[0]); + } + + return $propertyNames; + } + + private function shouldSkipClass(Class_ $class): bool + { + if (! $this->testsNodeAnalyzer->isInTestClass($class)) { + return true; } - return false; + // attribute must exist for the rule to work + if (! $this->reflectionProvider->hasClass(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)) { + return true; + } + + // already filled + if ($this->attributeFinder->hasAttributeByClasses( + $class, + [PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS] + )) { + return true; + } + + // has mock objects properties and setUp() method? + + $setupClassMethod = $class->getMethod(MethodName::SET_UP); + return ! $setupClassMethod instanceof ClassMethod; } } From 7bc815c945a23091f77c7df42f7ba3394477d7d1 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 27 Jan 2026 15:00:13 +0100 Subject: [PATCH 6/7] addd attribute if msising method --- ...ectsWithoutExpectationsAttributeRector.php | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php index 544afa53..e5d937bc 100644 --- a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -13,6 +13,8 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\ReflectionProvider; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; +use Rector\PhpParser\Node\BetterNodeFinder; +use Rector\PhpParser\NodeFinder\PropertyFetchFinder; use Rector\PHPUnit\Enum\PHPUnitAttribute; use Rector\PHPUnit\Enum\PHPUnitClassName; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; @@ -31,7 +33,8 @@ final class AllowMockObjectsWithoutExpectationsAttributeRector extends AbstractR public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly AttributeFinder $attributeFinder, - private readonly ReflectionProvider $reflectionProvider + private readonly ReflectionProvider $reflectionProvider, + private readonly BetterNodeFinder $betterNodeFinder, ) { } @@ -57,18 +60,29 @@ public function refactor(Node $node): ?Class_ } // @todo add the attribute if has more than 1 public test* method + $missedTestMethodsByMockPropertyName = []; $testMethodCount = 0; - foreach ($node->getMethods() as $classMethod) { - if ($this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { - // is a mock property used in the method? + foreach ($mockObjectPropertyNames as $mockObjectPropertyName) { + $missedTestMethodsByMockPropertyName[$mockObjectPropertyName] = []; + + foreach ($node->getMethods() as $classMethod) { + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } + + // is a mock property used in the class method, as part of some method call? guessing mock expectation is set // skip if so + if ($this->isClassMethodUsingMethodCallOnPropertyNamed($classMethod, $mockObjectPropertyName)) { + continue; + } + $missedTestMethodsByMockPropertyName[][] = $this->getName($classMethod); ++$testMethodCount; } } - if ($testMethodCount < 2) { + if (! $this->shouldAddAttribute($missedTestMethodsByMockPropertyName)) { return null; } @@ -187,4 +201,38 @@ private function shouldSkipClass(Class_ $class): bool $setupClassMethod = $class->getMethod(MethodName::SET_UP); return ! $setupClassMethod instanceof ClassMethod; } + + private function isClassMethodUsingMethodCallOnPropertyNamed(ClassMethod $classMethod, string $mockObjectPropertyName): bool + { + /** @var Node\Expr\MethodCall[] $methodCalls */ + $methodCalls = $this->betterNodeFinder->findInstancesOfScoped([$classMethod], [Node\Expr\MethodCall::class]); + foreach ($methodCalls as $methodCall) { + if (!$methodCall->var instanceof Node\Expr\PropertyFetch) { + continue; + } + + $propertyFetch = $methodCall->var; + + // we found a method call on a property fetch named + if ($this->isName($propertyFetch, $mockObjectPropertyName)) { + return true; + } + } + + return false; + } + + private function shouldAddAttribute(array $missedTestMethodsByMockPropertyName): bool + { + foreach ($missedTestMethodsByMockPropertyName as $propertyName => $missedTestMethods) { + // all test methods are using method calls on the mock property, so skip + if (count($missedTestMethods) === 0) { + continue; + } + + return true; + } + + return false; + } } From 1f788c17dba316e67ce8baf17e73b451e1023829 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 27 Jan 2026 14:00:51 +0000 Subject: [PATCH 7/7] [rector] Rector fixes --- config/sets/phpunit120.php | 1 - ...wMockObjectsWithoutExpectationsAttributeRector.php | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/sets/phpunit120.php b/config/sets/phpunit120.php index cd06fef1..5990bd7b 100644 --- a/config/sets/phpunit120.php +++ b/config/sets/phpunit120.php @@ -4,7 +4,6 @@ use Rector\Config\RectorConfig; use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector; -use Rector\PHPUnit\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\AssertIsTypeMethodCallRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\RemoveOverrideFinalConstructTestCaseRector; diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php index e5d937bc..91ba39f9 100644 --- a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -4,6 +4,8 @@ namespace Rector\PHPUnit\PHPUnit120\Rector\Class_; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup; @@ -14,7 +16,6 @@ use PHPStan\Reflection\ReflectionProvider; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; use Rector\PhpParser\Node\BetterNodeFinder; -use Rector\PhpParser\NodeFinder\PropertyFetchFinder; use Rector\PHPUnit\Enum\PHPUnitAttribute; use Rector\PHPUnit\Enum\PHPUnitClassName; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; @@ -204,10 +205,10 @@ private function shouldSkipClass(Class_ $class): bool private function isClassMethodUsingMethodCallOnPropertyNamed(ClassMethod $classMethod, string $mockObjectPropertyName): bool { - /** @var Node\Expr\MethodCall[] $methodCalls */ - $methodCalls = $this->betterNodeFinder->findInstancesOfScoped([$classMethod], [Node\Expr\MethodCall::class]); + /** @var MethodCall[] $methodCalls */ + $methodCalls = $this->betterNodeFinder->findInstancesOfScoped([$classMethod], [MethodCall::class]); foreach ($methodCalls as $methodCall) { - if (!$methodCall->var instanceof Node\Expr\PropertyFetch) { + if (!$methodCall->var instanceof PropertyFetch) { continue; } @@ -224,7 +225,7 @@ private function isClassMethodUsingMethodCallOnPropertyNamed(ClassMethod $classM private function shouldAddAttribute(array $missedTestMethodsByMockPropertyName): bool { - foreach ($missedTestMethodsByMockPropertyName as $propertyName => $missedTestMethods) { + foreach ($missedTestMethodsByMockPropertyName as $missedTestMethods) { // all test methods are using method calls on the mock property, so skip if (count($missedTestMethods) === 0) { continue;