From de0f5501c8d171e033309ecf2fcdc1dd01b9afa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orc=CC=A7un=20Candan?= Date: Sat, 3 Oct 2026 09:49:07 +0300 Subject: [PATCH] [PHPUnit120] Skip stub-only ->method() calls in AllowMockObjectsForDataProviderRector createStub() returns a Stub, which never requires expectations, so a data-provided test using only stubs must not get the #[AllowMockObjectsWithoutExpectations] attribute. Fixes rectorphp/rector#9920 Co-Authored-By: Claude Opus 5.5 --- .../Fixture/skip_stub_only.php.inc | 26 +++++++++++++++++++ .../NodeAnalyser/MockObjectExprDetector.php | 21 +++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsForDataProviderRector/Fixture/skip_stub_only.php.inc diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsForDataProviderRector/Fixture/skip_stub_only.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsForDataProviderRector/Fixture/skip_stub_only.php.inc new file mode 100644 index 00000000..393e5748 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsForDataProviderRector/Fixture/skip_stub_only.php.inc @@ -0,0 +1,26 @@ +createStub(\Countable::class); + $someStub->method('count')->willReturn($value); + + $this->assertSame($value, $someStub->count()); + } + + public static function someDataProvider(): iterable + { + yield [1]; + yield [2]; + } +} diff --git a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php index 5728845a..543d6656 100644 --- a/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php +++ b/rules/CodeQuality/NodeAnalyser/MockObjectExprDetector.php @@ -15,6 +15,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ObjectType; use Rector\NodeNameResolver\NodeNameResolver; +use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder; use Rector\PHPUnit\Enum\PHPUnitClassName; @@ -27,6 +28,7 @@ public function __construct( private NodeNameResolver $nodeNameResolver, private VariableFinder $variableFinder, private ReflectionResolver $reflectionResolver, + private NodeTypeResolver $nodeTypeResolver, ) { } @@ -49,6 +51,11 @@ public function hasMethodCallWithoutExpects(ClassMethod $classMethod): bool continue; } + // stubs, e.g. from createStub(), do not need expectations + if ($this->isStubOnly($methodCall->var)) { + continue; + } + return true; } @@ -165,4 +172,18 @@ public function isPropertyUsedForMocking(Class_ $class, string $propertyName): b return false; } + + private function isStubOnly(Expr $expr): bool + { + $exprType = $this->nodeTypeResolver->getType($expr); + + $stubObjectType = new ObjectType(PHPUnitClassName::STUB); + if (! $stubObjectType->isSuperTypeOf($exprType)->yes()) { + return false; + } + + $mockObjectType = new ObjectType(PHPUnitClassName::MOCK_OBJECT); + return ! $mockObjectType->isSuperTypeOf($exprType) + ->yes(); + } }