Skip to content

Commit

Permalink
[Laravel100] Adds ReplaceExpectsMethodsInTests rule (#162)
Browse files Browse the repository at this point in the history
* Adds ReplaceExpectsMethodsInTests rule
  • Loading branch information
peterfox authored Jan 3, 2024
1 parent db76992 commit b66b04d
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 2 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"config": {
"allow-plugins": {
"rector/extension-installer": true,
"phpstan/extension-installer": true
"phpstan/extension-installer": true,
"cweagans/composer-patches": false
}
}
}
4 changes: 4 additions & 0 deletions config/sets/laravel100.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\Renaming\ValueObject\RenameProperty;
use RectorLaravel\Rector\Cast\DatabaseExpressionCastsToMethodCallRector;
use RectorLaravel\Rector\Class_\ReplaceExpectsMethodsInTestsRector;
use RectorLaravel\Rector\Class_\UnifyModelDatesWithCastsRector;
use RectorLaravel\Rector\MethodCall\DatabaseExpressionToStringToMethodCallRector;

Expand All @@ -23,6 +24,9 @@
$rectorConfig->rule(DatabaseExpressionCastsToMethodCallRector::class);
$rectorConfig->rule(DatabaseExpressionToStringToMethodCallRector::class);

// https://github.com/laravel/framework/pull/41136/files
$rectorConfig->rule(ReplaceExpectsMethodsInTestsRector::class);

$rectorConfig
->ruleWithConfiguration(RenamePropertyRector::class, [
// https://github.com/laravel/laravel/commit/edcbe6de7c3f17070bf0ccaa2e2b785158ae5ceb
Expand Down
31 changes: 30 additions & 1 deletion docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 46 Rules Overview
# 47 Rules Overview

## AddArgumentDefaultValueRector

Expand Down Expand Up @@ -945,6 +945,35 @@ Removes the `$model` property from Factories.

<br>

## ReplaceExpectsMethodsInTestsRector

Replace expectJobs and expectEvents methods in tests

- class: [`RectorLaravel\Rector\Class_\ReplaceExpectsMethodsInTestsRector`](../src/Rector/Class_/ReplaceExpectsMethodsInTestsRector.php)

```diff
use Illuminate\Foundation\Testing\TestCase;

class SomethingTest extends TestCase
{
public function testSomething()
{
- $this->expectsJobs([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
- $this->expectsEvents(\App\Events\SomeEvent::class);
+ \Illuminate\Support\Facades\Bus::fake([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
+ \Illuminate\Support\Facades\Event::fake([\App\Events\SomeEvent::class]);

$this->get('/');
+
+ \Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeJob::class);
+ \Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeOtherJob::class);
+ \Illuminate\Support\Facades\Event::assertDispatched(\App\Events\SomeEvent::class);
}
}
```

<br>

## ReplaceFakerInstanceWithHelperRector

Replace `$this->faker` with the `fake()` helper function in Factories
Expand Down
179 changes: 179 additions & 0 deletions src/Rector/Class_/ReplaceExpectsMethodsInTestsRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Type\ObjectType;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\ReplaceExpectsMethodsInTestsRectorTest
*/
class ReplaceExpectsMethodsInTestsRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Replace expectJobs and expectEvents methods in tests', [
new CodeSample(
<<<'CODE_SAMPLE'
use Illuminate\Foundation\Testing\TestCase;
class SomethingTest extends TestCase
{
public function testSomething()
{
$this->expectsJobs([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
$this->expectsEvents(\App\Events\SomeEvent::class);
$this->get('/');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Illuminate\Foundation\Testing\TestCase;
class SomethingTest extends TestCase
{
public function testSomething()
{
\Illuminate\Support\Facades\Bus::fake([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
\Illuminate\Support\Facades\Event::fake([\App\Events\SomeEvent::class]);
$this->get('/');
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeJob::class);
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeOtherJob::class);
\Illuminate\Support\Facades\Event::assertDispatched(\App\Events\SomeEvent::class);
}
}
CODE_SAMPLE
),
]);
}

public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Node\Stmt\Class_ $node
*/
public function refactor(Node $node): ?Class_
{
if (! $this->isObjectType($node, new ObjectType('\Illuminate\Foundation\Testing\TestCase'))) {
return null;
}

$changes = false;

// loop over all methods in class
foreach ($node->getMethods() as $classMethod) {
// loop over all statements in method

$assertions = [];
foreach ($classMethod->getStmts() ?? [] as $index => $stmt) {
// if statement is not a method call, skip
if (! $stmt instanceof Expression) {
continue;
}

if (! $stmt->expr instanceof MethodCall) {
continue;
}

$methodCall = $stmt->expr;

// if method call is not expectsJobs or expectsEvents, skip
if (! $this->isNames($methodCall->name, ['expectsJobs', 'expectsEvents'])) {
continue;
}

// if method call is not in the form $this->expectsJobs(...), skip
if (! $methodCall->var instanceof Variable || ! $this->isName($methodCall->var, 'this')) {
continue;
}

if ($methodCall->args === []) {
continue;
}

// if the method call has a string constant as the first argument,
// convert it to an array
if ($methodCall->args[0] instanceof Arg && (
$methodCall->args[0]->value instanceof ClassConstFetch ||
$methodCall->args[0]->value instanceof String_
)) {
$args = new Array_([new ArrayItem($methodCall->args[0]->value)]);
} elseif (
$methodCall->args[0] instanceof Arg &&
$methodCall->args[0]->value instanceof Array_
) {
$args = $methodCall->args[0]->value;
} else {
continue;
}

if (! $methodCall->name instanceof Identifier) {
continue;
}

$facade = match ($methodCall->name->name) {
'expectsJobs' => 'Bus',
'expectsEvents' => 'Event',
default => null,
};

if ($facade === null) {
continue;
}

$replacement = new Expression(new StaticCall(
new FullyQualified('Illuminate\Support\Facades\\' . $facade),
'fake',
[new Arg($args)]
));

$classMethod->stmts[$index] = $replacement;

// generate assertDispatched calls for each argument
foreach ($args->items as $item) {
if ($item === null) {
continue;
}

$assertions[] = new Expression(new StaticCall(
new FullyQualified('Illuminate\Support\Facades\\' . $facade),
'assertDispatched',
[new Arg($item->value)]
));
}

$changes = true;
}

foreach ($assertions as $assertion) {
$classMethod->stmts[] = $assertion;
}
}

return $changes ? $node : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Source\TestCase;

class ApplyByInheritanceTest extends TestCase
{
public function testSomething()
{
$this->expectsJobs([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
$this->expectsEvents(\App\Events\SomeEvent::class);

$this->get('/');
}
}
?>
-----
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Source\TestCase;

class ApplyByInheritanceTest extends TestCase
{
public function testSomething()
{
\Illuminate\Support\Facades\Bus::fake([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
\Illuminate\Support\Facades\Event::fake([\App\Events\SomeEvent::class]);

$this->get('/');
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeJob::class);
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeOtherJob::class);
\Illuminate\Support\Facades\Event::assertDispatched(\App\Events\SomeEvent::class);
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use Illuminate\Foundation\Testing\TestCase;

class ApplyWithStringsTest extends TestCase
{
public function testSomething()
{
$this->expectsJobs(['\App\Jobs\SomeJob', '\App\Jobs\SomeOtherJob']);
$this->expectsEvents('\App\Events\SomeEvent');

$this->get('/');
}
}
?>
-----
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use Illuminate\Foundation\Testing\TestCase;

class ApplyWithStringsTest extends TestCase
{
public function testSomething()
{
\Illuminate\Support\Facades\Bus::fake(['\App\Jobs\SomeJob', '\App\Jobs\SomeOtherJob']);
\Illuminate\Support\Facades\Event::fake(['\App\Events\SomeEvent']);

$this->get('/');
\Illuminate\Support\Facades\Bus::assertDispatched('\App\Jobs\SomeJob');
\Illuminate\Support\Facades\Bus::assertDispatched('\App\Jobs\SomeOtherJob');
\Illuminate\Support\Facades\Event::assertDispatched('\App\Events\SomeEvent');
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use Illuminate\Foundation\Testing\TestCase;

class BaseTest extends TestCase
{
public function testSomething()
{
$this->expectsJobs([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
$this->expectsEvents(\App\Events\SomeEvent::class);

$this->get('/');
}
}
?>
-----
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use Illuminate\Foundation\Testing\TestCase;

class BaseTest extends TestCase
{
public function testSomething()
{
\Illuminate\Support\Facades\Bus::fake([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);
\Illuminate\Support\Facades\Event::fake([\App\Events\SomeEvent::class]);

$this->get('/');
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeJob::class);
\Illuminate\Support\Facades\Bus::assertDispatched(\App\Jobs\SomeOtherJob::class);
\Illuminate\Support\Facades\Event::assertDispatched(\App\Events\SomeEvent::class);
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\ReplaceExpectsMethodsInTestsRector\Fixture;

use Illuminate\Foundation\Testing\TestCase;

class NonExpectsMethodTest extends TestCase
{
public function testSomething()
{
$this->expectsSomething([\App\Jobs\SomeJob::class, \App\Jobs\SomeOtherJob::class]);

$this->get('/');
}
}
?>
Loading

0 comments on commit b66b04d

Please sign in to comment.