Skip to content

Commit d599620

Browse files
authored
[phpunit] Add NoAssertFuncCallInTestsRule (#175)
1 parent 286b971 commit d599620

File tree

8 files changed

+138
-0
lines changed

8 files changed

+138
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,17 @@ final class SomeTest extends TestCase
16691669

16701670
<br>
16711671

1672+
### NoAssertFuncCallInTestsRule
1673+
1674+
Avoid using assert*() functions in tests, as they can lead to false positives
1675+
1676+
```yaml
1677+
rules:
1678+
- Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule
1679+
```
1680+
1681+
<br>
1682+
16721683
### NoMockOnlyTestRule
16731684

16741685
Test should have at least one non-mocked property, to test something

config/phpunit-rules.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ rules:
44

55
- Symplify\PHPStanRules\Rules\Doctrine\NoDocumentMockingRule
66
- Symplify\PHPStanRules\Rules\Doctrine\NoEntityMockingRule
7+
8+
- Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
<directory>tests</directory>
1212
<exclude>tests/Rules/ClassNameRespectsParentSuffixRule/Fixture/</exclude>
1313
<exclude>tests/Rules/PHPUnit/PublicStaticDataProviderRule</exclude>
14+
<exclude>tests/Rules/PHPUnit/NoAssertFuncCallInTestsRule/Fixture</exclude>
1415
</testsuite>
1516
</phpunit>

src/Enum/PHPUnitRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ final class PHPUnitRuleIdentifier
1313
public const PUBLIC_STATIC_DATA_PROVIDER = 'phpunit.publicStaticDataProvider';
1414

1515
public const NO_MOCK_OBJECT_AND_REAL_OBJECT_PROPERTY = 'phpunit.noMockObjectAndRealObjectProperty';
16+
17+
public const NO_ASSERT_FUNC_CALL_IN_TESTS = 'phpunit.noAssertFuncCallInTests';
1618
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Rules\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Rules\IdentifierRuleError;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use Symplify\PHPStanRules\Enum\PHPUnitRuleIdentifier;
13+
14+
/**
15+
* @implements Rule<FuncCall>
16+
*/
17+
final class NoAssertFuncCallInTestsRule implements Rule
18+
{
19+
/**
20+
* @var string
21+
*/
22+
public const ERROR_MESSAGE = 'Instead of assert() that can miss important checks, use native PHPUnit assert call';
23+
24+
private const TEST_FILE_SUFFIXES = [
25+
'Test.php',
26+
'TestCase.php',
27+
'Context.php',
28+
];
29+
30+
public function getNodeType(): string
31+
{
32+
return FuncCall::class;
33+
}
34+
35+
/**
36+
* @param FuncCall $node
37+
* @return IdentifierRuleError[]
38+
*/
39+
public function processNode(Node $node, Scope $scope): array
40+
{
41+
if (! $node->name instanceof Name) {
42+
return [];
43+
}
44+
45+
if ($node->name->toString() !== 'assert') {
46+
return [];
47+
}
48+
49+
if (! $this->isTestFile($scope)) {
50+
return [];
51+
}
52+
53+
$identifierRuleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
54+
->identifier(PHPUnitRuleIdentifier::NO_ASSERT_FUNC_CALL_IN_TESTS)
55+
->build();
56+
57+
return [$identifierRuleError];
58+
}
59+
60+
private function isTestFile(Scope $scope): bool
61+
{
62+
foreach (self::TEST_FILE_SUFFIXES as $testFileSuffix) {
63+
if (str_ends_with($scope->getFile(), $testFileSuffix)) {
64+
return true;
65+
}
66+
}
67+
68+
return false;
69+
}
70+
}
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\NoAssertFuncCallInTestsRule\Fixture;
4+
5+
final class AssertFuncCallInsideTest
6+
{
7+
public function testMe(int $input)
8+
{
9+
assert($input === 100);
10+
}
11+
}
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\NoAssertFuncCallInTestsRule\Fixture;
4+
5+
final class SkipTestOutside
6+
{
7+
public function process(int $input)
8+
{
9+
assert($input === 100);
10+
}
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoAssertFuncCallInTestsRule;
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\NoAssertFuncCallInTestsRule;
10+
11+
final class NoAssertFuncCallInTestsRuleTest 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/AssertFuncCallInsideTest.php', [[NoAssertFuncCallInTestsRule::ERROR_MESSAGE, 9]]];
22+
23+
yield [__DIR__ . '/Fixture/SkipTestOutside.php', []];
24+
}
25+
26+
protected function getRule(): Rule
27+
{
28+
return new NoAssertFuncCallInTestsRule();
29+
}
30+
}

0 commit comments

Comments
 (0)