Skip to content

Commit f01d296

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

File tree

9 files changed

+322
-0
lines changed

9 files changed

+322
-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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 SkipMocking extends TestCase
9+
{
10+
public function test()
11+
{
12+
$mock = $this->createMock(\stdClass::class);
13+
$mock->expects($this->once())->method('someMethod')->willReturn('someValue');
14+
15+
$someObject = new ClassWithDependency($mock);
16+
$this->assertSame($mock, $someObject->getDependency());
17+
}
18+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 SkipUsedOutsideArg extends TestCase
9+
{
10+
public function test()
11+
{
12+
$mock = $this->createMock(\stdClass::class);
13+
14+
if ($mock instanceof \stdClass) {
15+
// do something
16+
}
17+
18+
$someObject = new ClassWithDependency($mock);
19+
$this->assertSame($mock, $someObject->getDependency());
20+
}
21+
}
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: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
/**
26+
* @see \Rector\PHPUnit\Tests\PHPUnit120\Rector\ClassMethod\ExpressionCreateMockToCreateStubRector\ExpressionCreateMockToCreateStubRectorTest
27+
*/
28+
final class ExpressionCreateMockToCreateStubRector extends AbstractRector
29+
{
30+
public function __construct(
31+
private readonly AssignedMocksCollector $assignedMocksCollector,
32+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
33+
private readonly VariableFinder $variableFinder,
34+
private readonly BetterNodeFinder $betterNodeFinder,
35+
) {
36+
}
37+
38+
public function getRuleDefinition(): RuleDefinition
39+
{
40+
return new RuleDefinition(
41+
'Replace createMock() assigned to variable that is only used as arg with no expectations, to createStub()',
42+
[
43+
new CodeSample(
44+
<<<'CODE_SAMPLE'
45+
use PHPUnit\Framework\TestCase;
46+
47+
final class SomeTest extends TestCase
48+
{
49+
public function test(): void
50+
{
51+
$mock = $this->createMock(SomeClass::class);
52+
53+
$someObject = new SomeClass($mock);
54+
$this->assertSame($mock, $someObject->getDependency());
55+
}
56+
}
57+
CODE_SAMPLE
58+
,
59+
<<<'CODE_SAMPLE'
60+
use PHPUnit\Framework\TestCase;
61+
62+
final class SomeTest extends TestCase
63+
{
64+
public function test(): void
65+
{
66+
$mock = $this->createStub(SomeClass::class);
67+
68+
$someObject = new SomeClass($mock);
69+
$this->assertSame($mock, $someObject->getDependency());
70+
}
71+
}
72+
CODE_SAMPLE
73+
),
74+
75+
]
76+
);
77+
}
78+
79+
public function getNodeTypes(): array
80+
{
81+
return [ClassMethod::class];
82+
}
83+
84+
/**
85+
* @param ClassMethod $node
86+
*/
87+
public function refactor(Node $node): ?ClassMethod
88+
{
89+
if (! $this->testsNodeAnalyzer->isTestClassMethod($node)) {
90+
return null;
91+
}
92+
93+
if ($node->stmts === null || count($node->stmts) < 2) {
94+
return null;
95+
}
96+
97+
$hasChanged = false;
98+
99+
foreach ($node->stmts as $stmt) {
100+
if (! $stmt instanceof Expression) {
101+
continue;
102+
}
103+
104+
if (! $stmt->expr instanceof Assign) {
105+
continue;
106+
}
107+
108+
$typeArg = $this->assignedMocksCollector->matchCreateMockArgAssignedToVariable($stmt->expr);
109+
if (! $typeArg instanceof Arg) {
110+
continue;
111+
}
112+
113+
/** @var Assign $assign */
114+
$assign = $stmt->expr;
115+
116+
if (! $assign->var instanceof Variable) {
117+
continue;
118+
}
119+
120+
$assignedVariable = $assign->var;
121+
$variableName = $this->getName($assignedVariable);
122+
if ($variableName === null) {
123+
continue;
124+
}
125+
126+
// find variable usages outside call like and inside it
127+
$usedVariables = $this->variableFinder->find($node, $variableName);
128+
129+
// used variable in calls
130+
/** @var array<StaticCall|MethodCall|New_> $callLikes */
131+
$callLikes = $this->betterNodeFinder->findInstancesOfScoped(
132+
$node->stmts,
133+
[MethodCall::class, StaticCall::class, New_::class]
134+
);
135+
136+
$callLikeUsedVariables = [];
137+
138+
foreach ($callLikes as $callLike) {
139+
foreach ($callLike->getArgs() as $arg) {
140+
if (! $arg->value instanceof Variable) {
141+
continue;
142+
}
143+
144+
if (! $this->isName($arg->value, $variableName)) {
145+
continue;
146+
}
147+
148+
$callLikeUsedVariables[] = $arg->value;
149+
}
150+
}
151+
152+
if (count($usedVariables) - 1 !== count($callLikeUsedVariables)) {
153+
continue;
154+
}
155+
156+
// here we can flip the createMock() to createStub()
157+
158+
if (! $assign->expr instanceof MethodCall) {
159+
continue;
160+
}
161+
162+
$methodCall = $assign->expr;
163+
$methodCall->name = new Identifier('createStub');
164+
165+
$hasChanged = true;
166+
}
167+
168+
if ($hasChanged) {
169+
return $node;
170+
}
171+
172+
return null;
173+
}
174+
}

0 commit comments

Comments
 (0)