Skip to content

Commit

Permalink
[CodeQuality] Add SingleMockPropertyTypeRector (#401)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba authored Nov 5, 2024
1 parent e54d4ad commit eb0b69a
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 1 deletion.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"symplify/easy-coding-standard": "^12.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-webmozart-assert": "^1.2",
"symplify/vendor-patches": "^11.2",
"symplify/vendor-patches": "^11.3",
"tracy/tracy": "^2.10",
"tomasvotruba/class-leak": "^1.0",
"rector/type-perfect": "^1.0",
Expand Down
2 changes: 2 additions & 0 deletions config/sets/phpunit-code-quality.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Rector\PHPUnit\CodeQuality\Rector\Class_\ConstructClassMethodToSetUpTestCaseRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\TestWithToDataProviderRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector;
use Rector\PHPUnit\CodeQuality\Rector\ClassMethod\DataProviderArrayItemsNewLinedRector;
Expand Down Expand Up @@ -84,5 +85,6 @@
* @see https://davegebler.com/post/php/better-php-unit-testing-avoiding-mocks
*/
RemoveExpectAnyFromMockRector::class,
SingleMockPropertyTypeRector::class,
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Source\AnyBook;

final class SkipNativeType extends TestCase
{
private AnyBook $anyBook;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Source\AnyBook;

class SkipSoleType extends TestCase
{
private MockObject $anyBook;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Source\AnyBook;

class UnionTypeMock extends TestCase
{
private MockObject|AnyBook $anyBook;
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Fixture;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Source\AnyBook;

class UnionTypeMock extends TestCase
{
private MockObject $anyBook;
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class SingleMockPropertyTypeRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\Source;

class AnyBook
{

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

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\PHPUnit\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector;

return RectorConfig::configure()
->withRules([SingleMockPropertyTypeRector::class]);
112 changes: 112 additions & 0 deletions rules/CodeQuality/Rector/Class_/SingleMockPropertyTypeRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\CodeQuality\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\UnionType;
use PHPUnit\Framework\MockObject\MockObject;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector\SingleMockPropertyTypeRectorTest
*/
final class SingleMockPropertyTypeRector extends AbstractRector
{
public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Make properties in tests with intersection mock object either object type or mock type',
[
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
final class MockingEntity extends TestCase
{
private SimpleObject|MockObject $someEntityMock;
protected function setUp(): void
{
$this->someEntityMock = $this->createMock(SimpleObject::class);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
final class MockingEntity extends TestCase
{
private MockObject $someEntityMock;
protected function setUp(): void
{
$this->someEntityMock = $this->createMock(SimpleObject::class);
}
}
CODE_SAMPLE
),
]
);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Class_
{
if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
return null;
}

$hasChanged = false;

foreach ($node->getProperties() as $property) {
if (! $property->type instanceof IntersectionType && ! $property->type instanceof UnionType) {
continue;
}

$complexType = $property->type;
if (count($complexType->types) !== 2) {
continue;
}

foreach ($complexType->types as $intersectionType) {
if ($this->isName($intersectionType, MockObject::class)) {
$property->type = $intersectionType;
$hasChanged = true;

break;
}
}
}

if (! $hasChanged) {
return null;
}

return $node;
}
}

0 comments on commit eb0b69a

Please sign in to comment.