Skip to content

Commit 97178a0

Browse files
authored
[phpunit] Add NoMockObjectAndRealObjectPropertyRule (#170)
1 parent f1fc3d2 commit 97178a0

File tree

10 files changed

+162
-0
lines changed

10 files changed

+162
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,17 @@ final class SomeController extends AbstractController
14991499

15001500
## 4. PHPUnit-specific Rules
15011501

1502+
### NoMockObjectAndRealObjectPropertyRule
1503+
1504+
Avoid using one property for both real object and mock object. Use separate properties or single type instead
1505+
1506+
```yaml
1507+
rules:
1508+
- Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule
1509+
```
1510+
1511+
<br>
1512+
15021513
### NoEntityMockingRule, NoDocumentMockingRule
15031514

15041515
Instead of entity or document mocking, create object directly to get better type support

src/Enum/PHPUnitRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ final class PHPUnitRuleIdentifier
1111
public const NO_MOCK_ONLY = 'phpunit.noMockOnly';
1212

1313
public const PUBLIC_STATIC_DATA_PROVIDER = 'phpunit.publicStaticDataProvider';
14+
15+
public const NO_MOCK_OBJECT_AND_REAL_OBJECT_PROPERTY = 'phpunit.noMockObjectAndRealObjectProperty';
1416
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Rules\PHPUnit;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\IntersectionType;
9+
use PhpParser\Node\Name;
10+
use PhpParser\Node\Stmt\Property;
11+
use PhpParser\Node\UnionType;
12+
use PHPStan\Analyser\Scope;
13+
use PHPStan\Rules\Rule;
14+
use PHPStan\Rules\RuleError;
15+
use PHPStan\Rules\RuleErrorBuilder;
16+
use PHPUnit\Framework\MockObject\MockObject;
17+
use Symplify\PHPStanRules\Enum\PHPUnitRuleIdentifier;
18+
19+
/**
20+
* @implements Rule<Property>
21+
*/
22+
final class NoMockObjectAndRealObjectPropertyRule implements Rule
23+
{
24+
/**
25+
* @var string
26+
*/
27+
public const ERROR_MESSAGE = 'Instead of ambiguous mock + object mix, pick single type that is more relevant';
28+
29+
public function getNodeType(): string
30+
{
31+
return Property::class;
32+
}
33+
34+
/**
35+
* @param Property $node
36+
* @return RuleError[]
37+
*/
38+
public function processNode(Node $node, Scope $scope): array
39+
{
40+
if (! $node->type instanceof IntersectionType && ! $node->type instanceof UnionType) {
41+
return [];
42+
}
43+
44+
foreach ($node->type->types as $type) {
45+
if (! $type instanceof Name) {
46+
continue;
47+
}
48+
49+
if ($type->toString() !== MockObject::class) {
50+
continue;
51+
}
52+
53+
return [RuleErrorBuilder::message(self::ERROR_MESSAGE)
54+
->identifier(PHPUnitRuleIdentifier::NO_MOCK_OBJECT_AND_REAL_OBJECT_PROPERTY)
55+
->build()];
56+
}
57+
58+
return [];
59+
}
60+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;
7+
8+
final class IntersectionMockedProperties
9+
{
10+
private MockObject&SomeObject $someObject;
11+
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;
7+
8+
final class SkipNullableObject
9+
{
10+
private ?MockObject $someObject;
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;
7+
8+
final class SkipOneOrTheOther
9+
{
10+
private SomeObject $someObject;
11+
12+
private MockObject $anotherMock;
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;
7+
8+
final class SomeTestWithMockedProperties
9+
{
10+
private MockObject|SomeObject $someObject;
11+
12+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;
4+
5+
use Iterator;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;
10+
11+
final class NoMockObjectAndRealObjectPropertyRuleTest extends RuleTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function testRule(string $filePath, array $expectedErrorsWithLines): void
15+
{
16+
$this->analyse([$filePath], $expectedErrorsWithLines);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
yield [__DIR__ . '/Fixture/SomeTestWithMockedProperties.php', [[NoMockObjectAndRealObjectPropertyRule::ERROR_MESSAGE, 10]]];
22+
yield [__DIR__ . '/Fixture/IntersectionMockedProperties.php', [[NoMockObjectAndRealObjectPropertyRule::ERROR_MESSAGE, 10]]];
23+
24+
yield [__DIR__ . '/Fixture/SkipOneOrTheOther.php', []];
25+
yield [__DIR__ . '/Fixture/SkipNullableObject.php', []];
26+
}
27+
28+
protected function getRule(): Rule
29+
{
30+
return new NoMockObjectAndRealObjectPropertyRule();
31+
}
32+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source;
4+
5+
final class SomeObject
6+
{
7+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule

0 commit comments

Comments
 (0)