Skip to content

Commit e18abe4

Browse files
committed
add variable support to CreateStubOverCreateMockArgRector
1 parent 4608de8 commit e18abe4

File tree

9 files changed

+356
-92
lines changed

9 files changed

+356
-92
lines changed

config/sets/phpunit-code-quality.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\UseSpecificWithMethodRector;
5151
use Rector\PHPUnit\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector;
5252
use Rector\PHPUnit\CodeQuality\Rector\StmtsAwareInterface\DeclareStrictTypesTestsRector;
53+
use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector;
54+
use Rector\PHPUnit\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector;
5355
use Rector\PHPUnit\PHPUnit60\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector;
5456
use Rector\PHPUnit\PHPUnit90\Rector\MethodCall\ReplaceAtMethodWithDesiredMatcherRector;
5557
use Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector;
@@ -125,6 +127,10 @@
125127
SimplerWithIsInstanceOfRector::class,
126128
DirectInstanceOverMockArgRector::class,
127129

130+
// stub over mock
131+
CreateStubOverCreateMockArgRector::class,
132+
ExpressionCreateMockToCreateStubRector::class,
133+
128134
// @test first, enable later
129135
// \Rector\PHPUnit\CodeQuality\Rector\Expression\ConfiguredMockEntityToSetterObjectRector::class,
130136

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class HandleAssignedVariable extends TestCase
8+
{
9+
public function testThat()
10+
{
11+
$itemMock = $this->createMock(\stdClass::class);
12+
13+
$items = [
14+
$itemMock
15+
];
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
24+
25+
use PHPUnit\Framework\TestCase;
26+
27+
final class HandleAssignedVariable extends TestCase
28+
{
29+
public function testThat()
30+
{
31+
$itemMock = $this->createStub(\stdClass::class);
32+
33+
$items = [
34+
$itemMock
35+
];
36+
}
37+
}
38+
39+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class HandleAssignedVariableMultipleTimes extends TestCase
8+
{
9+
public function testThat()
10+
{
11+
$itemMock = $this->createMock(\stdClass::class);
12+
13+
$items = [
14+
$itemMock
15+
];
16+
17+
$anotherValue = new \stdClass($itemMock);
18+
}
19+
}
20+
21+
?>
22+
-----
23+
<?php
24+
25+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
26+
27+
use PHPUnit\Framework\TestCase;
28+
29+
final class HandleAssignedVariableMultipleTimes extends TestCase
30+
{
31+
public function testThat()
32+
{
33+
$itemMock = $this->createStub(\stdClass::class);
34+
35+
$items = [
36+
$itemMock
37+
];
38+
39+
$anotherValue = new \stdClass($itemMock);
40+
}
41+
}
42+
43+
?>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;
9+
10+
final class HandleUsedOutsideArg extends TestCase
11+
{
12+
public function test()
13+
{
14+
$mock = $this->createMock(\stdClass::class);
15+
16+
if ($mock instanceof \stdClass) {
17+
// do something
18+
}
19+
20+
$someObject = new ClassWithDependency($mock);
21+
$this->assertSame($mock, $someObject->getDependency());
22+
}
23+
}
24+
25+
?>
26+
-----
27+
<?php
28+
29+
declare(strict_types=1);
30+
31+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;
32+
33+
use PHPUnit\Framework\TestCase;
34+
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;
35+
36+
final class HandleUsedOutsideArg extends TestCase
37+
{
38+
public function test()
39+
{
40+
$mock = $this->createStub(\stdClass::class);
41+
42+
if ($mock instanceof \stdClass) {
43+
// do something
44+
}
45+
46+
$someObject = new ClassWithDependency($mock);
47+
$this->assertSame($mock, $someObject->getDependency());
48+
}
49+
}
50+
51+
?>

rules-tests/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector/Fixture/skip_used_outside_arg.php.inc

Lines changed: 0 additions & 21 deletions
This file was deleted.

rules/CodeQuality/NodeAnalyser/AssertMethodAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function detectTestCaseCall(MethodCall|StaticCall $call): bool
3030
? $call->var
3131
: $call->class;
3232

33-
if (! $this->nodeTypeResolver->isObjectType($objectCaller, new ObjectType('PHPUnit\Framework\TestCase'))) {
33+
if (! $this->nodeTypeResolver->isObjectType($objectCaller, new ObjectType(PHPUnitClassName::TEST_CASE))) {
3434
return false;
3535
}
3636

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\NodeAnalyser;
6+
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Expr\StaticCall;
10+
use PhpParser\Node\Expr\Variable;
11+
use PhpParser\Node\Stmt\ClassMethod;
12+
use Rector\NodeNameResolver\NodeNameResolver;
13+
use Rector\PhpParser\Node\BetterNodeFinder;
14+
use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder;
15+
16+
final readonly class MockObjectExprDetector
17+
{
18+
public function __construct(
19+
private BetterNodeFinder $betterNodeFinder,
20+
private NodeNameResolver $nodeNameResolver,
21+
private VariableFinder $variableFinder,
22+
) {
23+
}
24+
25+
public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
26+
{
27+
if (! $expr instanceof Variable) {
28+
return false;
29+
}
30+
31+
$variableName = $this->nodeNameResolver->getName($expr);
32+
33+
// to be safe
34+
if ($variableName === null) {
35+
return true;
36+
}
37+
38+
$relatedVariables = $this->variableFinder->find($classMethod, $variableName);
39+
40+
// only self variable found, nothing to mock
41+
if (count($relatedVariables) === 1) {
42+
return false;
43+
}
44+
45+
// find out, how many are used in call likes as args
46+
/** @var array<Expr\MethodCall> $methodCalls */
47+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, [MethodCall::class]);
48+
49+
foreach ($methodCalls as $methodCall) {
50+
if (! $methodCall->var instanceof Variable) {
51+
continue;
52+
}
53+
54+
if ($this->nodeNameResolver->isName($methodCall->var, $variableName)) {
55+
// variable is being called on, most like mocking, lets skip
56+
return true;
57+
}
58+
}
59+
60+
return false;
61+
}
62+
63+
public function isUsedInAssert(Expr $expr, ClassMethod $classMethod): bool
64+
{
65+
if (! $expr instanceof Variable) {
66+
return false;
67+
}
68+
69+
$variableName = $this->nodeNameResolver->getName($expr);
70+
71+
// to be safe
72+
if ($variableName === null) {
73+
return true;
74+
}
75+
76+
/** @var array<MethodCall|StaticCall> $callLikes */
77+
$callLikes = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, [MethodCall::class, StaticCall::class]);
78+
79+
foreach ($callLikes as $callLike) {
80+
$methodName = $this->nodeNameResolver->getName($callLike->name);
81+
if (! str_starts_with((string) $methodName, 'assert')) {
82+
continue;
83+
}
84+
85+
foreach ($callLike->args as $arg) {
86+
if (! $arg->value instanceof Variable) {
87+
continue;
88+
}
89+
90+
if (! $this->nodeNameResolver->isName($arg->value, $variableName)) {
91+
continue;
92+
}
93+
94+
return true;
95+
}
96+
}
97+
98+
return false;
99+
}
100+
}

0 commit comments

Comments
 (0)