Skip to content

Commit 27de243

Browse files
committed
addd attribute if msising method
1 parent 009fea0 commit 27de243

File tree

1 file changed

+53
-5
lines changed

1 file changed

+53
-5
lines changed

rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use PhpParser\Node\Stmt\ClassMethod;
1414
use PHPStan\Reflection\ReflectionProvider;
1515
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
16+
use Rector\PhpParser\Node\BetterNodeFinder;
17+
use Rector\PhpParser\NodeFinder\PropertyFetchFinder;
1618
use Rector\PHPUnit\Enum\PHPUnitAttribute;
1719
use Rector\PHPUnit\Enum\PHPUnitClassName;
1820
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
@@ -31,7 +33,8 @@ final class AllowMockObjectsWithoutExpectationsAttributeRector extends AbstractR
3133
public function __construct(
3234
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
3335
private readonly AttributeFinder $attributeFinder,
34-
private readonly ReflectionProvider $reflectionProvider
36+
private readonly ReflectionProvider $reflectionProvider,
37+
private readonly BetterNodeFinder $betterNodeFinder,
3538
) {
3639
}
3740

@@ -57,18 +60,29 @@ public function refactor(Node $node): ?Class_
5760
}
5861

5962
// @todo add the attribute if has more than 1 public test* method
63+
$missedTestMethodsByMockPropertyName = [];
6064
$testMethodCount = 0;
6165

62-
foreach ($node->getMethods() as $classMethod) {
63-
if ($this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
64-
// is a mock property used in the method?
66+
foreach ($mockObjectPropertyNames as $mockObjectPropertyName) {
67+
$missedTestMethodsByMockPropertyName[$mockObjectPropertyName] = [];
68+
69+
foreach ($node->getMethods() as $classMethod) {
70+
if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
71+
continue;
72+
}
73+
74+
// is a mock property used in the class method, as part of some method call? guessing mock expectation is set
6575
// skip if so
76+
if ($this->isClassMethodUsingMethodCallOnPropertyNamed($classMethod, $mockObjectPropertyName)) {
77+
continue;
78+
}
6679

80+
$missedTestMethodsByMockPropertyName[][] = $this->getName($classMethod);
6781
++$testMethodCount;
6882
}
6983
}
7084

71-
if ($testMethodCount < 2) {
85+
if (! $this->shouldAddAttribute($missedTestMethodsByMockPropertyName)) {
7286
return null;
7387
}
7488

@@ -187,4 +201,38 @@ private function shouldSkipClass(Class_ $class): bool
187201
$setupClassMethod = $class->getMethod(MethodName::SET_UP);
188202
return ! $setupClassMethod instanceof ClassMethod;
189203
}
204+
205+
private function isClassMethodUsingMethodCallOnPropertyNamed(ClassMethod $classMethod, string $mockObjectPropertyName): bool
206+
{
207+
/** @var Node\Expr\MethodCall[] $methodCalls */
208+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped([$classMethod], [Node\Expr\MethodCall::class]);
209+
foreach ($methodCalls as $methodCall) {
210+
if (!$methodCall->var instanceof Node\Expr\PropertyFetch) {
211+
continue;
212+
}
213+
214+
$propertyFetch = $methodCall->var;
215+
216+
// we found a method call on a property fetch named
217+
if ($this->isName($propertyFetch, $mockObjectPropertyName)) {
218+
return true;
219+
}
220+
}
221+
222+
return false;
223+
}
224+
225+
private function shouldAddAttribute(array $missedTestMethodsByMockPropertyName): bool
226+
{
227+
foreach ($missedTestMethodsByMockPropertyName as $propertyName => $missedTestMethods) {
228+
// all test methods are using method calls on the mock property, so skip
229+
if (count($missedTestMethods) === 0) {
230+
continue;
231+
}
232+
233+
return true;
234+
}
235+
236+
return false;
237+
}
190238
}

0 commit comments

Comments
 (0)