psalm / plugin-phpunit
Psalm plugin for PHPUnit
Package info
github.com/psalm/psalm-plugin-phpunit
Language:Gherkin
Type:psalm-plugin
pkg:composer/psalm/plugin-phpunit
Requires
- php: >=8.2
- ext-simplexml: *
- psalm/psalm-plugin-api: ^0.4.2
- vimeo/psalm: ^7.2 || dev-master
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.88
- phpspec/prophecy-phpunit: ^2.5
- phpunit/phpunit: ^11.0 || ^12.0 || ^13.0
Suggests
None
Provides
None
Conflicts
- phpspec/prophecy: <1.20.0
- phpspec/prophecy-phpunit: <2.3.0
- phpunit/phpunit: <11.0
Replaces
None
- dev-master / 1.x-dev
- 1.1.0
- 1.0.0
- 0.22.x-dev
- 0.22.2
- 0.22.1
- 0.22.0
- 0.21.1
- 0.21.0
- 0.20.4
- 0.20.3
- 0.20.2
- 0.20.1
- 0.20.0
- 0.19.7
- 0.19.5
- 0.19.4
- 0.19.3
- 0.19.2
- 0.19.0
- 0.18.4
- 0.18.3
- 0.18.2
- 0.18.1
- 0.18.0
- 0.17.0
- 0.16.1
- 0.16.0
- 0.15.2
- 0.15.1
- 0.15.0
- 0.14.0
- 0.13.0
- 0.12.2
- 0.12.1
- 0.12.0
- 0.11.0
- 0.10.1
- 0.10.0
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.1
- 0.8.0
- 0.7.0
- 0.6.0
- 0.5.8
- 0.5.7
- 0.5.6
- 0.5.5
- 0.5.4
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.1
- 0.4.0
- 0.3.0
- 0.2.0
- 0.1.1
- 0.1.0
This package is auto-updated.
Last update: 2026-11-11 13:01:00 UTC
README
Psalm PHPUnit plugin
Teaches Psalm the conventions of PHPUnit, so analysing your test suite stops producing false positives.
Installation
Psalm 7: nothing to install. The plugin ships with Psalm and is enabled automatically when your composer.json requires phpunit/phpunit (see loadLibraryPlugins).
Psalm 6 and older:
composer require --dev psalm/plugin-phpunit
vendor/bin/psalm-plugin enable psalm/plugin-phpunit
Features
Plugin issues are suppressed by name: <PluginIssue name="MissingParentCall" errorLevel="suppress"/>.
Data providers and datasets
Providers (#[DataProvider], #[DataProviderExternal], #[DataProviderClosure], @dataProvider) and their datasets are checked against the test method. A provider that is not public static is an InvalidDataProvider. Named datasets are matched to parameters by name (InvalidNamedArgument, TooFewArguments). Parameters take the types of the datasets.
public static function amounts(): iterable { yield [100]; yield ['100']; } #[DataProvider('amounts')] // InvalidArgument public function testAmount(int $cents): void {} #[DataProvider('amunts')] // UndefinedMethod public function testTypo(int $cents): void {}
Test discovery and unused code
Tests, providers and TestCase classes are not unused code. Properties set in setUp() or a #[Before] method are initialized (no MissingConstructor). Exceptions declared by PHPUnit's API are not MissingThrowsDocblock.
$this->expectException(InvalidArgumentException::class); strtoupper($name); // no UnusedFunctionCall
Coverage targets
Classes, methods and functions in coverage attributes (and @covers on PHPUnit 11 and older) must exist.
#[CoversMethod(Invoice::class, 'totl')] // UndefinedMethod #[CoversFunction('format_money')] // UndefinedFunction
Assertion suggestions
Assertions that a dedicated one expresses better are reported as UnspecificNullAssertion, UnspecificBooleanAssertion, UnspecificCountAssertion and UnspecificEqualsAssertion. They are errors at errorLevel="1" and info at higher levels.
self::assertSame(null, $maybe); // UnspecificNullAssertion self::assertSame(true, $flag); // UnspecificBooleanAssertion self::assertSame(3, count($items)); // UnspecificCountAssertion self::assertEquals(1, $int); // UnspecificEqualsAssertion
Psalter fixes the null, boolean and equals ones.
vendor/bin/psalter --issues=UnspecificNullAssertion,UnspecificBooleanAssertion,UnspecificEqualsAssertion
Mocks and doubles
Methods configured on a mock or stub must exist on the mocked class. Intersection doubles and Prophecy calls get their proper types.
$mailer = $this->createMock(Mailer::class); $mailer->method('sendd'); // UndefinedMethod
Docblock metadata
PHPUnit 12 and newer ignore docblock metadata, so it is an IgnoredDocblockMetadata. On older versions, annotations PHPUnit cannot parse (@dataProvider with no value) are an InvalidPhpUnitAnnotation.
/** @test */ // IgnoredDocblockMetadata public function itWorks(): void {}
Parent calls in hooks
When a base test case declares setUp(), tearDown() or another fixture hook, overrides must call parent::, or they are a MissingParentCall.
final class OrderTest extends DatabaseTestCase { protected function setUp(): void {} // MissingParentCall }
Type narrowing
assertNotEmpty() narrows its argument (?Customer becomes Customer).
self::assertNotEmpty($customer); $customer->name(); // no issue
License
MIT