Skip to content

Commit 6e454b1

Browse files
committed
[phpunit 12] Add AllowMockObjectsWithoutExpectationsAttributeRector
1 parent fcc50b9 commit 6e454b1

File tree

8 files changed

+292
-0
lines changed

8 files changed

+292
-0
lines changed

config/sets/phpunit120.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
declare(strict_types=1);
44

5+
use Rector\PHPUnit\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector;
56
use Rector\Config\RectorConfig;
67
use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector;
78
use Rector\PHPUnit\PHPUnit120\Rector\Class_\AssertIsTypeMethodCallRector;
@@ -14,5 +15,9 @@
1415

1516
// stubs over mocks
1617
CreateStubOverCreateMockArgRector::class,
18+
19+
// experimental, from PHPUnit 12.5.2
20+
// @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43
21+
AllowMockObjectsWithoutExpectationsAttributeRector::class,
1722
]);
1823
};
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\Class_\AllowMockObjectsWithoutExpectationsAttributeRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AllowMockObjectsWithoutExpectationsAttributeRectorTest 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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SkipOnlySingleTest extends TestCase
8+
{
9+
private \PHPUnit\Framework\MockObject\MockObject $someMock;
10+
11+
protected function setUp(): void
12+
{
13+
$this->someMock = $this->createMock(\stdClass::class);
14+
}
15+
16+
public function testOne()
17+
{
18+
}
19+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class SomeClass extends TestCase
8+
{
9+
private \PHPUnit\Framework\MockObject\MockObject $someMock;
10+
11+
protected function setUp(): void
12+
{
13+
$this->someMock = $this->createMock(\stdClass::class);
14+
}
15+
16+
public function testOne()
17+
{
18+
}
19+
20+
public function testTwo()
21+
{
22+
23+
}
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector\Fixture;
31+
32+
use PHPUnit\Framework\TestCase;
33+
34+
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
35+
final class SomeClass extends TestCase
36+
{
37+
private \PHPUnit\Framework\MockObject\MockObject $someMock;
38+
39+
protected function setUp(): void
40+
{
41+
$this->someMock = $this->createMock(\stdClass::class);
42+
}
43+
44+
public function testOne()
45+
{
46+
}
47+
48+
public function testTwo()
49+
{
50+
51+
}
52+
}
53+
54+
?>
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\Class_\AllowMockObjectsWithoutExpectationsAttributeRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AllowMockObjectsWithoutExpectationsAttributeRector::class]);
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\PHPUnit120\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Attribute;
9+
use PhpParser\Node\AttributeGroup;
10+
use PhpParser\Node\Name;
11+
use PhpParser\Node\Name\FullyQualified;
12+
use PhpParser\Node\Stmt\Class_;
13+
use PhpParser\Node\Stmt\ClassMethod;
14+
use PHPStan\Reflection\ReflectionProvider;
15+
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
16+
use Rector\PHPUnit\Enum\PHPUnitAttribute;
17+
use Rector\PHPUnit\Enum\PHPUnitClassName;
18+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
19+
use Rector\Rector\AbstractRector;
20+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
21+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
22+
23+
/**
24+
* @see \Rector\PHPUnit\Tests\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector\AllowMockObjectsWithoutExpectationsAttributeRectorTest
25+
*
26+
* @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43
27+
*/
28+
final class AllowMockObjectsWithoutExpectationsAttributeRector extends AbstractRector
29+
{
30+
public function __construct(
31+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
32+
private readonly AttributeFinder $attributeFinder,
33+
private readonly ReflectionProvider $reflectionProvider
34+
) {
35+
}
36+
37+
public function getNodeTypes(): array
38+
{
39+
return [Class_::class];
40+
}
41+
42+
/**
43+
* @param Class_ $node
44+
*/
45+
public function refactor(Node $node): ?Class_
46+
{
47+
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
48+
return null;
49+
}
50+
51+
// attribute must exist for the rule to work
52+
if (! $this->reflectionProvider->hasClass(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)) {
53+
return null;
54+
}
55+
56+
// already filled
57+
if ($this->attributeFinder->hasAttributeByClasses(
58+
$node,
59+
[PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS]
60+
)) {
61+
return null;
62+
}
63+
64+
// has mock objects properties and setUp() method?
65+
66+
if (! $node->getMethod('setUp') instanceof ClassMethod) {
67+
return null;
68+
}
69+
70+
if (! $this->hasMockObjectProperty($node)) {
71+
return null;
72+
}
73+
74+
// @todo add the attribute if has more than 1 public test* method
75+
$testMethodCount = 0;
76+
77+
foreach ($node->getMethods() as $classMethod) {
78+
if ($this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
79+
++$testMethodCount;
80+
}
81+
}
82+
83+
if ($testMethodCount < 2) {
84+
return null;
85+
}
86+
87+
// add attribute
88+
$node->attrGroups[] = new AttributeGroup([
89+
new Attribute(new FullyQualified(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)),
90+
]);
91+
92+
return $node;
93+
}
94+
95+
public function getRuleDefinition(): RuleDefinition
96+
{
97+
return new RuleDefinition(
98+
'Add #[AllowMockObjectsWithoutExpectations] attribute to PHPUnit test classes with mock properties used in multiple methods',
99+
[
100+
new CodeSample(
101+
<<<'CODE_SAMPLE'
102+
use PHPUnit\Framework\TestCase;
103+
final class SomeTest extends TestCase
104+
{
105+
private \PHPUnit\Framework\MockObject\MockObject $someServiceMock;
106+
107+
protected function setUp(): void
108+
{
109+
$this->someServiceMock = $this->createMock(SomeService::class);
110+
}
111+
112+
public function testOne(): void
113+
{
114+
// use $this->someServiceMock
115+
}
116+
117+
public function testTwo(): void
118+
{
119+
// use $this->someServiceMock
120+
}
121+
}
122+
CODE_SAMPLE
123+
,
124+
<<<'CODE_SAMPLE'
125+
use PHPUnit\Framework\TestCase;
126+
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
127+
128+
#[AllowMockObjectsWithoutExpectations]
129+
final class SomeTest extends TestCase
130+
{
131+
private \PHPUnit\Framework\MockObject\MockObject $someServiceMock;
132+
133+
protected function setUp(): void
134+
{
135+
$this->someServiceMock = $this->createMock(SomeService::class);
136+
}
137+
138+
public function testOne(): void
139+
{
140+
// use $this->someServiceMock
141+
}
142+
143+
public function testTwo(): void
144+
{
145+
// use $this->someServiceMock
146+
}
147+
}
148+
CODE_SAMPLE
149+
),
150+
151+
]
152+
);
153+
154+
}
155+
156+
private function hasMockObjectProperty(Class_ $class): bool
157+
{
158+
foreach ($class->getProperties() as $property) {
159+
if (! $property->type instanceof Name) {
160+
continue;
161+
}
162+
163+
if ($this->isName($property->type, PHPUnitClassName::MOCK_OBJECT)) {
164+
return true;
165+
}
166+
}
167+
168+
return false;
169+
}
170+
}

src/Enum/PHPUnitAttribute.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ final class PHPUnitAttribute
2323
public const string REQUIRES_SETTING = 'PHPUnit\Framework\Attributes\RequiresSetting';
2424

2525
public const string TEST = 'PHPUnit\Framework\Attributes\Test';
26+
27+
/**
28+
* @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43
29+
*/
30+
public const string ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS = 'PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations';
2631
}

src/Enum/PHPUnitClassName.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ final class PHPUnitClassName
2323

2424
public const string TEST_LISTENER = 'PHPUnit\Framework\TestListener';
2525

26+
public const string MOCK_OBJECT = 'PHPUnit\Framework\MockObject\MockObject';
27+
2628
/**
2729
* @var string[]
2830
*/

0 commit comments

Comments
 (0)