Menu

Testing

Your custom extensions should have unit tests in order to prevent bugs sneaking into your code.

PHPStan supports testing with the industry standard PHPUnit framework.

Custom rules #

Custom rules can be tested in a test case extended from PHPStan\Testing\RuleTestCase. A typical test can look like this:

<?php declare(strict_types = 1);

namespace App\PHPStan;

use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<MyRule>
*/

class MyRuleTest extends RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
// getRule() method needs to return an instance of the tested rule
return new MyRule();
}

public function testRule(): void
{
// first argument: path to the example file that contains some errors that should be reported by MyRule
// second argument: an array of expected errors,
// each error consists of the asserted error message, and the asserted error file line
$this->analyse([__DIR__ . '/data/my-rule.php'], [
[
'X should not be Y', // asserted error message
15, // asserted error line
],
]);

// the test fails, if the expected error does not occur,
// or if there are other errors reported beside the expected one
}

public static function getAdditionalConfigFiles(): array
{
// path to your project's phpstan.neon, or extension.neon in case of custom extension packages
// this is only necessary if your custom rule relies on some extra configuration and other extensions
return [__DIR__ . '/../extension.neon'];
}

}

Type inference #

Other extension types, like dynamic return type extensions or type-specifying extensions, do not report errors by themselves, but rather influence the inferred types by the analysis engine so that PHPStan understands the analysed code better, remove false-positives[1], and narrow down the types to report more useful errors.

A typical type inference test case class might look like this:

<?php declare(strict_types = 1);

namespace App\PHPStan;

use PHPStan\Testing\TypeInferenceTestCase;

class MyContainerDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
{

/**
* @return iterable<mixed>
*/

public function dataFileAsserts(): iterable
{
// path to a file with actual asserts of expected types:
yield from $this->gatherAssertTypes(__DIR__ . '/data/my-container-types.php');
}

/**
* @dataProvider dataFileAsserts
*/

public function testFileAsserts(
string $assertType,
string $file,
mixed ...$args
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
// path to your project's phpstan.neon, or extension.neon in case of custom extension packages
return [__DIR__ . '/../extension.neon'];
}

}

And the referenced file looks like normal analysed code, but takes advantage of the \PHPStan\Testing\assertType() function with the type assertions:

<?php

namespace MyTypesTest;

use function PHPStan\Testing\assertType;

class Foo
{

public function doFoo(\App\MyContainer $container): void
{
// arguments: string with the expected type, actual type
assertType(FooService::class, $container->getService('foo'));
assertType(BarService::class, $container->getService('bar'));

assertType('bool', $container->getParameter('isProduction'));
}

}

  1. An error that’s reported but isn’t actually a bug in the analysed code. ↩︎

Edit this page on GitHub

© 2016–2023 Ondřej Mirtes