psalm/plugin-phpunit
Psalm plugin that understands PHPUnit tests to improve static analysis accuracy. Adds PHPUnit-aware stubs and type inference so assertions, mocks, and test helpers are checked correctly. Requires Psalm v4+. Install via Composer and enable with psalm-plugin.
composer require --dev psalm/plugin-phpunit
vendor/bin/psalm-plugin enable psalm/plugin-phpunit
vendor/bin/psalm --init
vendor/bin/psalm --no-cache --output-format=github
Run Psalm on a test file with PHPUnit assertions to see immediate improvements:
vendor/bin/psalm --no-cache --output-format=github tests/Feature/ExampleTest.php
Expected Outcome: Reduced false positives in assertions (e.g., assertSame(), assertInstanceOf()) and proper handling of @dataProvider annotations.
assertSame($expected, $actual) where $expected is int but $actual is string).createMock(Service::class) with incorrect expectations).vendor/bin/psalm --init
vendor/bin/psalm --no-cache --output-format=github tests/
@dataProvider or #[DataProvider] with complex return types (e.g., iterable<list<mixed>>).
#[DataProvider]
public static function provideTestData(): iterable {
yield ['input' => 'test', 'expected' => 123];
yield ['input' => 'data', 'expected' => 456];
}
public function testExample(string $input, int $expected): void {
$this->assertSame($expected, someFunction($input));
}
public function testMockInteraction(): void {
$mock = $this->createMock(Service::class);
$mock->method('process')
->willReturn('result');
$this->assertSame('result', $mock->process());
}
willReturn() with wrong type).Http::fake(), DatabaseMigrations::run()):
public function testApiEndpoint(): void {
Http::fake([
'api.example.com' => Http::response('{"status": "ok"}'),
]);
$response = $this->get('/endpoint');
$this->assertJson(['status' => 'ok']);
}
assertJson() or assertSee() with dynamic responses.Http::fake()).# .github/workflows/psalm.yml
jobs:
psalm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: composer install
- run: vendor/bin/psalm --no-cache --output-format=github tests/
Psalm Version Mismatch:
0.20.0). Using older versions (e.g., 0.19.x) may cause crashes or incomplete analysis.composer require --dev psalm/psalm "^7.0"
Unsupported PHPUnit Features:
composer require --dev phpunit/phpunit "^9.0"
phpunit.xml to use PHPUnit 9+.False Positives with Dynamic Data Providers:
iterable<mixed> return types from data providers as "non-iterable" if not properly typed.#[DataProvider]
public static function provideData(): iterable {
yield ['key' => 'value']; // Psalm infers `iterable<array-key, array-value>`
}
Sticky Data Providers:
@dataProvider annotation.--no-cache to force re-analysis:
vendor/bin/psalm --no-cache
Prophecy Mocks:
Argument::that() with closures) may trigger false positives.@psalm-suppress for edge cases:
$mock->method('process')
->willReturnCallback(fn($arg) => $arg + 1);
// @psalm-suppress MixedArgument
Enable Verbose Output:
vendor/bin/psalm --verbose --no-cache
Isolate Test Files:
vendor/bin/psalm tests/Feature/ExampleTest.php
Check Plugin Compatibility:
psalm/plugin-phpunit is enabled in psalm-plugin.json:
{
"plugins": ["psalm/plugin-phpunit"]
}
Custom Data Provider Types:
// stubs/Your/DataProviderStub.php
namespace Your;
class DataProviderStub {
/**
* @return iterable<array{string, int}>
*/
public static function provideCustomData(): iterable {}
}
Suppress Specific Warnings:
@psalm-suppress for known false positives:
#[Test]
public function testEdgeCase(): void {
// @psalm-suppress MixedReturnStatement
$result = someUnstableFunction();
$this->assertTrue($result);
}
Integrate with Laravel’s Test Helpers:
DatabaseMigrations::run()):
// stubs/Laravel/Test/DatabaseMigrations.php
namespace Laravel\Test;
class DatabaseMigrations {
public static function run(): void {}
}
--parallel for faster analysis:
vendor/bin/psalm --parallel --no-cache
php -d memory_limit=2G vendor/bin/psalm
How can I help you explore Laravel packages today?