PHPackages                             dave-liddament/phpstan-rule-test-helper - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. dave-liddament/phpstan-rule-test-helper

ActiveLibrary[Testing &amp; Quality](/categories/testing)

dave-liddament/phpstan-rule-test-helper
=======================================

Library to help make testing of PHPStan rules easier

0.6.0(5mo ago)027.8k↑435.1%11MITPHPPHP ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0CI passing

Since Dec 29Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/DaveLiddament/phpstan-rule-test-helper)[ Packagist](https://packagist.org/packages/dave-liddament/phpstan-rule-test-helper)[ RSS](/packages/dave-liddament-phpstan-rule-test-helper/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)Dependencies (4)Versions (11)Used By (1)

PHPStan rule testing helper
===========================

[](#phpstan-rule-testing-helper)

[![PHP versions: 8.0|8.1|8.2|8.3|8.4|8.5](https://camo.githubusercontent.com/f91073cd9f141638de858f06805c0403f0cffbac2774e412a7b0b609a02829f8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e30253743382e31253743382e32253743382e33253743382e34253743382e352d626c75652e737667)](https://packagist.org/packages/dave-liddament/phpstan-rule-test-helper)[![Latest Stable Version](https://camo.githubusercontent.com/790e923fcaba18a4514dec07d64847d9c5e08a891ecb654fcdb4b336de253600/68747470733a2f2f706f7365722e707567782e6f72672f646176652d6c696464616d656e742f7068707374616e2d72756c652d746573742d68656c7065722f762f737461626c65)](https://packagist.org/packages/dave-liddament/phpstan-rule-test-helper)[![License](https://camo.githubusercontent.com/71fe3a8b5204c349acfe8276bed26df2cd9e3b8413c16ce82794bc6417c5ad77/68747470733a2f2f706f7365722e707567782e6f72672f646176652d6c696464616d656e742f7068707374616e2d72756c652d746573742d68656c7065722f6c6963656e7365)](https://github.com/DaveLiddament/phpstan-rule-test-helper/blob/master/LICENSE.md)[![Total Downloads](https://camo.githubusercontent.com/cc04e49ec810883916ca966550d6a95c42d72bf39d78af5fdcf7c014a5ecf32c/68747470733a2f2f706f7365722e707567782e6f72672f646176652d6c696464616d656e742f7068707374616e2d72756c652d746573742d68656c7065722f646f776e6c6f616473)](https://packagist.org/packages/dave-liddament/phpstan-rule-test-helper/stats)

[![Continuous Integration](https://github.com/DaveLiddament/phpstan-rule-test-helper/workflows/Full%20checks/badge.svg)](https://github.com/DaveLiddament/phpstan-rule-test-helper/actions)[![PHPStan level max](https://camo.githubusercontent.com/ecb39a33957e802f1f085f1debada1e99904e72b8d807e98991fb7f9660cb6d3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782532306c6576656c2d627269676874677265656e2e737667)](https://github.com/DaveLiddament/phpstan-rule-test-helper/blob/master/phpstan.neon)

This library offers a couple of improvements to PHPStan's [custom rule test harness](https://phpstan.org/developing-extensions/testing#custom-rules).

This library provides [AbstractRuleTestCase](src/AbstractRuleTestCase.php), which extends PHPStan's `RuleTestCase`.

It offers a simpler way to write tests for custom rules. Specifically:

1. No need to specify line numbers in the test code.
2. You can specify the expected error message once.

Improvement 1: No more line numbers in tests
--------------------------------------------

[](#improvement-1-no-more-line-numbers-in-tests)

The minimal test case specifies the Rule being tested and at least one test. Each test must call the `assertIssuesReported` method, which takes the path of one or more fixture files.

#### Test code:

[](#test-code)

```
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;

class CallableFromRuleTest extends AbstractRuleTestCase
{
    protected function getRule(): Rule
    {
        return new CallableFromRule($this->createReflectionProvider());
    }

    public function testAllowedCall(): void
    {
        $this->assertIssuesReported(__DIR__ . '/Fixtures/SomeCode.php');
    }
}
```

The fixture file contains the expected error message.

#### Fixture:

[](#fixture)

```
class SomeCode
{
    public function go(): void
    {
        $item = new Item("hello");
        $item->updateName("world");  // ERROR Can not call method
    }
}
```

Every line that contains `// ERROR ` is considered an issue that should be picked up by the rule. The text after `// ERROR` is the expected error message.

With this approach you don't need to work out the line number of the error. This is particularly handy when you update the Fixture file, you no longer have to update all the line numbers in the test.

Improvement 2: Specify the expected error message once
======================================================

[](#improvement-2-specify-the-expected-error-message-once)

Often you end up writing the same error message for every violation. To get round this use the `getErrorFromatter` method to specify the error message.

#### Test code:

[](#test-code-1)

```
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;

class CallableFromRuleTest extends AbstractRuleTestCase
{
    // `getRule` and `testAllowedCall` methods are as above and are omitted for brevity

    protected function getErrorFormatter(): string
    {
        return "Can not call method";
    }
}
```

The fixture file is simplified as there is no need to specify the error message. Any lines where an error is expected need to end with `// ERROR`, the expected error message is taken from the `getErrorFormatter` method.

#### Fixture:

[](#fixture-1)

```
class SomeCode
{
    public function go(): void
    {
        $item = new Item("hello");
        $item->updateName("world");  // ERROR
    }

    public function go2(): void
    {
        $item = new Item("hello");
        $item->remove();  // ERROR
    }
}
```

The expected error messages would be:

- Line 6: `Can not call method`
- Line 12: `Can not call method`

The benefits of this approach are no duplication of the error message text. Any changes to the error message only need to be made in one place in the test case.

### Adding context to error messages

[](#adding-context-to-error-messages)

Good error message require context. The context is added to the fixture file after `// ERROR `. Multiple pieces of context can be added by separating them with the `|` character.

#### Test code:

[](#test-code-2)

```
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;

class CallableFromRuleTest extends AbstractRuleTestCase
{
    // `getRule` and `testAllowedCall` methods are as above and are omitted for brevity

    protected function getErrorFormatter(): string
    {
        return "Can not call {0} from within class {1}";
    }
}
```

#### Fixture:

[](#fixture-2)

```
class SomeCode
{
    public function go(): void
    {
        $item = new Item("hello");
        $item->updateName("world");  // ERROR Item::updateName|SomeCode
    }

    public function go2(): void
    {
        $item = new Item("hello");
        $item->remove();  // ERROR Item::remove|SomeCode
    }
}
```

The expected error messages would be:

- Line 6: `Can not call Item::updateName from within class SomeCode`
- Line 11: `Can not call Item::remove from within class SomeCode`

### More flexible error messages

[](#more-flexible-error-messages)

If you need more flexibility in the error message, you can return an object that implements the `ErrorMessageFormatter` [interface](src/ErrorMessageFormatter.php).

In the example below the message changes depending on the number of parts in the error context.

NOTE: This is a contrived example, but it shows how you can use `ErrorMessageFormatter` to create more flexible error messages.

```
use DaveLiddament\PhpstanRuleTestHelper\AbstractRuleTestCase;

class CallableFromRuleTest extends AbstractRuleTestCase
{
    // `getRule` and `testAllowedCall` methods omitted are as above and are for brevity

    protected function getErrorFormatter(): ErrorMessageFormatter
    {
        retrun new class() extends ErrorMessageFormatter {
            public function getErrorMessage(string $errorContext): string
            {
                $parts = $this->getErrorMessageAsParts($errorContext);
                $calledFrom = count($parts) === 2 ?  'class '.$parts[1] : 'outside an object';

                return sprintf('Can not call %s from %s', $parts[0], $calledFrom);
            }
        };
   }
}
```

#### Fixture:

[](#fixture-3)

```
class SomeCode
{
    public function go(): void
    {
        $item = new Item("hello");
        $item->updateName("world");  // ERROR Item::updateName|SomeCode
    }
}

$item = new Item("hello");
$item->remove();  // ERROR Item::remove
```

The expected error messages would be:

- Line 6: `Can not call Item::updateName from class SomeCode`
- Line 11: `Can not call Item::remove from outside an object`

Installation
------------

[](#installation)

```
composer require --dev dave-liddament/phpstan-rule-test-helper
```

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance70

Regular maintenance activity

Popularity30

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 95.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~213 days

Recently: every ~180 days

Total

6

Last Release

170d ago

PHP version history (4 changes)0.1.0PHP &gt;=8.0 &lt;8.3

0.2.0PHP ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0

0.4.0PHP ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0

0.6.0PHP ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/dc81fab8d878316b9117eb3a8fe3abece1d25a37c3099d76c6f3651f82991505?d=identicon)[DaveLiddament](/maintainers/DaveLiddament)

---

Top Contributors

[![DaveLiddament](https://avatars.githubusercontent.com/u/6787687?v=4)](https://github.com/DaveLiddament "DaveLiddament (39 commits)")[![doppynl](https://avatars.githubusercontent.com/u/814475?v=4)](https://github.com/doppynl "doppynl (1 commits)")[![Nyholm](https://avatars.githubusercontent.com/u/1275206?v=4)](https://github.com/Nyholm "Nyholm (1 commits)")

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/dave-liddament-phpstan-rule-test-helper/health.svg)

```
[![Health](https://phpackages.com/badges/dave-liddament-phpstan-rule-test-helper/health.svg)](https://phpackages.com/packages/dave-liddament-phpstan-rule-test-helper)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[phpstan/phpstan-symfony

Symfony Framework extensions and rules for PHPStan

78268.9M1.5k](/packages/phpstan-phpstan-symfony)[phpstan/phpstan-doctrine

Doctrine extensions for PHPStan

66466.6M1.1k](/packages/phpstan-phpstan-doctrine)[phpat/phpat

PHP Architecture Tester

1.2k3.5M32](/packages/phpat-phpat)[spaze/phpstan-disallowed-calls

PHPStan rules to detect disallowed method &amp; function calls, constant, namespace, attribute, property &amp; superglobal usages, with powerful rules to re-allow a call or a usage in places where it should be allowed.

33120.0M375](/packages/spaze-phpstan-disallowed-calls)[mglaman/phpstan-drupal

Drupal extension and rules for PHPStan

20729.0M124](/packages/mglaman-phpstan-drupal)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
