Skip to content

Commit 9a9f3f4

Browse files
committed
[phpunit 12] Add /ExpressionCreateMockToCreateStubRector
1 parent 74914f2 commit 9a9f3f4

File tree

7 files changed

+278
-0
lines changed

7 files changed

+278
-0
lines changed

config/sets/phpunit120.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector;
77
use Rector\PHPUnit\PHPUnit120\Rector\Class_\AssertIsTypeMethodCallRector;
88
use Rector\PHPUnit\PHPUnit120\Rector\Class_\RemoveOverrideFinalConstructTestCaseRector;
9+
use Rector\PHPUnit\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector;
910

1011
return static function (RectorConfig $rectorConfig): void {
1112
$rectorConfig->rules([
@@ -14,6 +15,7 @@
1415

1516
// stubs over mocks
1617
CreateStubOverCreateMockArgRector::class,
18+
ExpressionCreateMockToCreateStubRector::class,
1719

1820
// experimental, from PHPUnit 12.5.2
1921
// @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Source;
4+
5+
final class InstanceWithMock
6+
{
7+
public function __construct(private $object)
8+
{
9+
}
10+
11+
public function getInner(): object
12+
{
13+
return $this->object;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class ExpressionCreateMockToCreateStubRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
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\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;
7+
8+
final class SomeTest extends TestCase
9+
{
10+
public function test()
11+
{
12+
$mock = $this->createMock(\stdClass::class);
13+
14+
$someObject = new ClassWithDependency($mock);
15+
$this->assertSame($mock, $someObject->getDependency());
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Fixture;
24+
25+
use PHPUnit\Framework\TestCase;
26+
use Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source\ClassWithDependency;
27+
28+
final class SomeTest extends TestCase
29+
{
30+
public function test()
31+
{
32+
$mock = $this->createStub(\stdClass::class);
33+
34+
$someObject = new ClassWithDependency($mock);
35+
$this->assertSame($mock, $someObject->getDependency());
36+
}
37+
}
38+
39+
?>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\Source;
4+
5+
final class ClassWithDependency
6+
{
7+
public function __construct(
8+
private $dependency,
9+
) {
10+
}
11+
12+
public function getDependency()
13+
{
14+
return $this->dependency;
15+
}
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector;
7+
8+
return RectorConfig::configure()
9+
->withRules(rules: [ExpressionCreateMockToCreateStubRector::class]);
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\PHPUnit120\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\Assign;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Expr\New_;
12+
use PhpParser\Node\Expr\StaticCall;
13+
use PhpParser\Node\Expr\Variable;
14+
use PhpParser\Node\Identifier;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use PhpParser\Node\Stmt\Expression;
17+
use Rector\PhpParser\Node\BetterNodeFinder;
18+
use Rector\PHPUnit\CodeQuality\NodeAnalyser\AssignedMocksCollector;
19+
use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder;
20+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
21+
use Rector\Rector\AbstractRector;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
final class ExpressionCreateMockToCreateStubRector extends AbstractRector
26+
{
27+
public function __construct(
28+
private readonly AssignedMocksCollector $assignedMocksCollector,
29+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
30+
private readonly VariableFinder $variableFinder,
31+
private readonly BetterNodeFinder $betterNodeFinder,
32+
) {
33+
}
34+
35+
public function getRuleDefinition(): RuleDefinition
36+
{
37+
return new RuleDefinition(
38+
'Replace createMock() assigned to variable that is only used as arg with no expectations, to createStub()',
39+
[
40+
new CodeSample(
41+
<<<'CODE_SAMPLE'
42+
use PHPUnit\Framework\TestCase;
43+
44+
final class SomeTest extends TestCase
45+
{
46+
public function test(): void
47+
{
48+
$mock = $this->createMock(SomeClass::class);
49+
50+
$someObject = new SomeClass($mock);
51+
$this->assertSame($mock, $someObject->getDependency());
52+
}
53+
}
54+
CODE_SAMPLE
55+
,
56+
<<<'CODE_SAMPLE'
57+
use PHPUnit\Framework\TestCase;
58+
59+
final class SomeTest extends TestCase
60+
{
61+
public function test(): void
62+
{
63+
$mock = $this->createStub(SomeClass::class);
64+
65+
$someObject = new SomeClass($mock);
66+
$this->assertSame($mock, $someObject->getDependency());
67+
}
68+
}
69+
CODE_SAMPLE
70+
),
71+
72+
]
73+
);
74+
}
75+
76+
public function getNodeTypes(): array
77+
{
78+
return [ClassMethod::class];
79+
}
80+
81+
/**
82+
* @param ClassMethod $node
83+
*/
84+
public function refactor(Node $node)
85+
{
86+
if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) {
87+
return null;
88+
}
89+
90+
if ($node->stmts === null || count($node->stmts) < 2) {
91+
return null;
92+
}
93+
94+
$hasChanged = false;
95+
96+
foreach ($node->stmts as $stmt) {
97+
if (! $stmt instanceof Expression) {
98+
continue;
99+
}
100+
101+
if (! $stmt->expr instanceof Assign) {
102+
continue;
103+
}
104+
105+
$typeArg = $this->assignedMocksCollector->matchCreateMockArgAssignedToVariable($stmt->expr);
106+
if (! $typeArg instanceof Arg) {
107+
continue;
108+
}
109+
110+
/** @var Assign $assign */
111+
$assign = $stmt->expr;
112+
113+
if (! $assign->var instanceof Variable) {
114+
continue;
115+
}
116+
117+
$assignedVariable = $assign->var;
118+
$variableName = $this->getName($assignedVariable);
119+
120+
// find variable usages outside call like and inside it
121+
$usedVariables = $this->variableFinder->find($node, $variableName);
122+
123+
// used variable in calls
124+
/** @var array<StaticCall|MethodCall|New_> $callLikes */
125+
$callLikes = $this->betterNodeFinder->findInstancesOfScoped(
126+
$node->stmts,
127+
[MethodCall::class, StaticCall::class, New_::class]
128+
);
129+
130+
$callLikeUsedVariables = [];
131+
132+
foreach ($callLikes as $callLike) {
133+
foreach ($callLike->getArgs() as $arg) {
134+
if (! $arg->value instanceof Variable) {
135+
continue;
136+
}
137+
138+
if (! $this->isName($arg->value, $variableName)) {
139+
continue;
140+
}
141+
142+
$callLikeUsedVariables[] = $arg->value;
143+
}
144+
}
145+
146+
dump(count($callLikeUsedVariables));
147+
if (count($usedVariables) - 1 !== count($callLikeUsedVariables)) {
148+
continue;
149+
}
150+
151+
// here we can flip the createMock() to createStub()
152+
153+
if (! $assign->expr instanceof MethodCall) {
154+
continue;
155+
}
156+
157+
$methodCall = $assign->expr;
158+
$methodCall->name = new Identifier('createStub');
159+
160+
$hasChanged = true;
161+
}
162+
163+
if ($hasChanged) {
164+
return $node;
165+
}
166+
167+
return null;
168+
}
169+
}

0 commit comments

Comments
 (0)