Skip to content

Commit

Permalink
[phpunit] Add NoMockObjectAndRealObjectPropertyRule (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba authored Feb 12, 2025
1 parent f1fc3d2 commit 97178a0
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 0 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,17 @@ final class SomeController extends AbstractController

## 4. PHPUnit-specific Rules

### NoMockObjectAndRealObjectPropertyRule

Avoid using one property for both real object and mock object. Use separate properties or single type instead

```yaml
rules:
- Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule
```

<br>

### NoEntityMockingRule, NoDocumentMockingRule

Instead of entity or document mocking, create object directly to get better type support
Expand Down
2 changes: 2 additions & 0 deletions src/Enum/PHPUnitRuleIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ final class PHPUnitRuleIdentifier
public const NO_MOCK_ONLY = 'phpunit.noMockOnly';

public const PUBLIC_STATIC_DATA_PROVIDER = 'phpunit.publicStaticDataProvider';

public const NO_MOCK_OBJECT_AND_REAL_OBJECT_PROPERTY = 'phpunit.noMockObjectAndRealObjectProperty';
}
60 changes: 60 additions & 0 deletions src/Rules/PHPUnit/NoMockObjectAndRealObjectPropertyRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Rules\PHPUnit;

use PhpParser\Node;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\UnionType;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use Symplify\PHPStanRules\Enum\PHPUnitRuleIdentifier;

/**
* @implements Rule<Property>
*/
final class NoMockObjectAndRealObjectPropertyRule implements Rule
{
/**
* @var string
*/
public const ERROR_MESSAGE = 'Instead of ambiguous mock + object mix, pick single type that is more relevant';

public function getNodeType(): string
{
return Property::class;
}

/**
* @param Property $node
* @return RuleError[]
*/
public function processNode(Node $node, Scope $scope): array
{
if (! $node->type instanceof IntersectionType && ! $node->type instanceof UnionType) {
return [];
}

foreach ($node->type->types as $type) {
if (! $type instanceof Name) {
continue;
}

if ($type->toString() !== MockObject::class) {
continue;
}

return [RuleErrorBuilder::message(self::ERROR_MESSAGE)
->identifier(PHPUnitRuleIdentifier::NO_MOCK_OBJECT_AND_REAL_OBJECT_PROPERTY)
->build()];
}

return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;

final class IntersectionMockedProperties
{
private MockObject&SomeObject $someObject;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;

final class SkipNullableObject
{
private ?MockObject $someObject;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;

final class SkipOneOrTheOther
{
private SomeObject $someObject;

private MockObject $anotherMock;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source\SomeObject;

final class SomeTestWithMockedProperties
{
private MockObject|SomeObject $someObject;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;

final class NoMockObjectAndRealObjectPropertyRuleTest extends RuleTestCase
{
#[DataProvider('provideData')]
public function testRule(string $filePath, array $expectedErrorsWithLines): void
{
$this->analyse([$filePath], $expectedErrorsWithLines);
}

public static function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/SomeTestWithMockedProperties.php', [[NoMockObjectAndRealObjectPropertyRule::ERROR_MESSAGE, 10]]];
yield [__DIR__ . '/Fixture/IntersectionMockedProperties.php', [[NoMockObjectAndRealObjectPropertyRule::ERROR_MESSAGE, 10]]];

yield [__DIR__ . '/Fixture/SkipOneOrTheOther.php', []];
yield [__DIR__ . '/Fixture/SkipNullableObject.php', []];
}

protected function getRule(): Rule
{
return new NoMockObjectAndRealObjectPropertyRule();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule\Source;

final class SomeObject
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rules:
- Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule

0 comments on commit 97178a0

Please sign in to comment.