Skip to content

Strict and opinionated rules for PHPUnit #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ It also contains this framework-specific rule (can be enabled separately):

* Check that both values passed to `assertSame()` method are of the same type.

It also contains this strict framework-specific rules (can be enabled separately):

* Check that you are not using `assertSame()` with `true` as expected value. `assertTrue()` should be used instead.
* Check that you are not using `assertSame()` with `false` as expected value. `assertFalse()` should be used instead.
* Check that you are not using `assertSame()` with `null` as expected value. `assertNull()` should be used instead.
* Check that you are not using `assertSame()` with `count($variable)` as second parameter. `assertCount($variable)` should be used instead.

## How to document mock objects in phpDocs?

If you need to configure the mock even after you assign it to a property or return it from a method, you should add `PHPUnit_Framework_MockObject_MockObject` to the phpDoc:
Expand Down Expand Up @@ -81,3 +88,9 @@ To perform framework-specific checks, include also this file:
```
- vendor/phpstan/phpstan-phpunit/rules.neon
```

To perform addition strict PHPUnit checks, include also this file:

```
- vendor/phpstan/phpstan-phpunit/strictRules.neon
```
2 changes: 0 additions & 2 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
<arg value="--extensions=php"/>
<arg value="--encoding=utf-8"/>
<arg value="--tab-width=4"/>
<arg value="--ignore=tests/*/data"/>
<arg value="-sp"/>
<arg path="src"/>
<arg path="tests"/>
Expand All @@ -59,7 +58,6 @@
<arg value="--extensions=php"/>
<arg value="--encoding=utf-8"/>
<arg value="--tab-width=4"/>
<arg value="--ignore=tests/*/data"/>
<arg value="-sp"/>
<arg path="src"/>
<arg path="tests"/>
Expand Down
1 change: 1 addition & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@
<property name="enableVoidTypeHint" type="false" />
</properties>
</rule>
<exclude-pattern>tests/*/data</exclude-pattern>
</ruleset>
52 changes: 52 additions & 0 deletions src/Rules/PHPUnit/AssertRuleHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;

class AssertRuleHelper
{

/**
* @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return bool
*/
public static function isMethodOrStaticCallOnTestCase(Node $node, Scope $scope): bool
{
$testCaseType = new ObjectType(\PHPUnit\Framework\TestCase::class);
if ($node instanceof Node\Expr\MethodCall) {
$calledOnType = $scope->getType($node->var);
} elseif ($node instanceof Node\Expr\StaticCall) {
if ($node->class instanceof Node\Name) {
$class = (string) $node->class;
if (in_array(
strtolower($class),
[
'self',
'static',
'parent',
],
true
)) {
$calledOnType = new ObjectType($scope->getClassReflection()->getName());
} else {
$calledOnType = new ObjectType($class);
}
} else {
$calledOnType = $scope->getType($node->class);
}
} else {
return false;
}

if (!$testCaseType->isSuperTypeOf($calledOnType)->yes()) {
return false;
}

return true;
}

}
53 changes: 53 additions & 0 deletions src/Rules/PHPUnit/AssertSameBooleanExpectedRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Type\FalseBooleanType;
use PHPStan\Type\TrueBooleanType;

class AssertSameBooleanExpectedRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\NodeAbstract::class;
}

/**
* @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[] errors
*/
public function processNode(Node $node, Scope $scope): array
{
if (!AssertRuleHelper::isMethodOrStaticCallOnTestCase($node, $scope)) {
return [];
}

if (count($node->args) < 2) {
return [];
}
if (!is_string($node->name) || strtolower($node->name) !== 'assertsame') {
return [];
}

$leftType = $scope->getType($node->args[0]->value);

if ($leftType instanceof TrueBooleanType) {
return [
'You should use assertTrue() instead of assertSame() when expecting "true"',
];
}

if ($leftType instanceof FalseBooleanType) {
return [
'You should use assertFalse() instead of assertSame() when expecting "false"',
];
}

return [];
}

}
29 changes: 1 addition & 28 deletions src/Rules/PHPUnit/AssertSameDifferentTypesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;

class AssertSameDifferentTypesRule implements \PHPStan\Rules\Rule
{
Expand All @@ -21,33 +20,7 @@ public function getNodeType(): string
*/
public function processNode(Node $node, Scope $scope): array
{
$testCaseType = new ObjectType(\PHPUnit\Framework\TestCase::class);
if ($node instanceof Node\Expr\MethodCall) {
$calledOnType = $scope->getType($node->var);
} elseif ($node instanceof Node\Expr\StaticCall) {
if ($node->class instanceof Node\Name) {
$class = (string) $node->class;
if (in_array(
strtolower($class),
[
'self',
'static',
'parent',
],
true
)) {
$calledOnType = new ObjectType($scope->getClassReflection()->getName());
} else {
$calledOnType = new ObjectType($class);
}
} else {
$calledOnType = $scope->getType($node->class);
}
} else {
return [];
}

if (!$testCaseType->isSuperTypeOf($calledOnType)->yes()) {
if (!AssertRuleHelper::isMethodOrStaticCallOnTestCase($node, $scope)) {
return [];
}

Expand Down
46 changes: 46 additions & 0 deletions src/Rules/PHPUnit/AssertSameNullExpectedRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Type\NullType;

class AssertSameNullExpectedRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\NodeAbstract::class;
}

/**
* @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[] errors
*/
public function processNode(Node $node, Scope $scope): array
{
if (!AssertRuleHelper::isMethodOrStaticCallOnTestCase($node, $scope)) {
return [];
}

if (count($node->args) < 2) {
return [];
}
if (!is_string($node->name) || strtolower($node->name) !== 'assertsame') {
return [];
}

$leftType = $scope->getType($node->args[0]->value);

if ($leftType instanceof NullType) {
return [
'You should use assertNull() instead of assertSame(null, $actual).',
];
}

return [];
}

}
49 changes: 49 additions & 0 deletions src/Rules/PHPUnit/AssertSameWithCountRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;

class AssertSameWithCountRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return \PhpParser\NodeAbstract::class;
}

/**
* @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[] errors
*/
public function processNode(Node $node, Scope $scope): array
{
if (!AssertRuleHelper::isMethodOrStaticCallOnTestCase($node, $scope)) {
return [];
}

if (count($node->args) < 2) {
return [];
}
if (!is_string($node->name) || strtolower($node->name) !== 'assertsame') {
return [];
}

$right = $node->args[1]->value;

if (
$right instanceof Node\Expr\FuncCall
&& $right->name instanceof Node\Name
&& strtolower($right->name->toString()) === 'count'
) {
return [
'You should use assertCount($expectedCount, $variable) instead of assertSame($expectedCount, count($variable)).',
];
}

return [];
}

}
13 changes: 13 additions & 0 deletions strictRules.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
-
class: PHPStan\Rules\PHPUnit\AssertSameBooleanExpectedRule
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\PHPUnit\AssertSameNullExpectedRule
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\PHPUnit\AssertSameWithCountRule
tags:
- phpstan.rules.rule
37 changes: 37 additions & 0 deletions tests/Rules/PHPUnit/AssertSameBooleanExpectedRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PHPStan\Rules\Rule;

class AssertSameBooleanExpectedRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): Rule
{
return new AssertSameBooleanExpectedRule();
}

public function testRule()
{
$this->analyse([__DIR__ . '/data/assert-same-boolean-expected.php'], [
[
'You should use assertTrue() instead of assertSame() when expecting "true"',
10,
],
[
'You should use assertFalse() instead of assertSame() when expecting "false"',
11,
],
[
'You should use assertTrue() instead of assertSame() when expecting "true"',
14,
],
[
'You should use assertFalse() instead of assertSame() when expecting "false"',
17,
],
]);
}

}
14 changes: 13 additions & 1 deletion tests/Rules/PHPUnit/AssertSameDifferentTypesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,21 @@ public function testRule()
'Call to assertSame() with different types array<int, string> and array<int, int> will always result in test failure.',
14,
],
[
'Call to assertSame() with different types string and int will always result in test failure.',
16,
],
[
'Call to assertSame() with different types string and int will always result in test failure.',
17,
],
[
'Call to assertSame() with different types string and int will always result in test failure.',
18,
],
[
'Call to assertSame() with different types array<string> and array<int> will always result in test failure.',
35,
39,
],
]);
}
Expand Down
29 changes: 29 additions & 0 deletions tests/Rules/PHPUnit/AssertSameNullExpectedRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PHPUnit;

use PHPStan\Rules\Rule;

class AssertSameNullExpectedRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): Rule
{
return new AssertSameNullExpectedRule();
}

public function testRule()
{
$this->analyse([__DIR__ . '/data/assert-same-null-expected.php'], [
[
'You should use assertNull() instead of assertSame(null, $actual).',
10,
],
[
'You should use assertNull() instead of assertSame(null, $actual).',
13,
],
]);
}

}
Loading