psalm/plugin-phpunit
A Psalm plugin for PHPUnit that improves static analysis of test suites by teaching Psalm about PHPUnit-specific APIs, assertions, and mocking. Install with Composer, enable via psalm-plugin, and get more accurate type checking in your tests. Requires Psalm v4 or later.
Release reviewed: 0.21.1 (published 2026-03-19) — supersedes the assessment baseline 0.20.0.
Verdict: Maintenance-only release. No breaking changes, no new analyzer features, no deprecations affecting consumers.
actions/cache 5→6, actions/checkout 6→7, symfony/yaml 6.4.18→6.4.48 inside /tools/behat. All confined to the plugin's own CI/Behat harness. Zero consumer-facing impact.#[Test], #[DataProvider], #[Before]), critical for modern Laravel testing (e.g., Laravel's #[Test] usage under phpunit:10).assertSame(), assertInstanceOf()), improving accuracy for Laravel's Eloquent, API response, and service container tests.composer require --dev psalm/plugin-phpunit
vendor/bin/psalm-plugin enable psalm/plugin-phpunit
psalm.xml (add <dir>tests/</dir> to <file-list>). No Laravel-specific overrides needed.^0.20 to ^0.21. Since 0.x semver permits breaking changes on minor bumps, pin to ^0.21 (not ^0) and review the changelog on every minor../vendor/bin/psalm).| Risk Area | Assessment | Mitigation |
|---|---|---|
| Psalm Version Lock | Plugin requires Psalm v5+; legacy Laravel projects may lag. Unchanged by 0.21.1. | Audit composer.json for Psalm version; upgrade if <v5. Enforce with --strict. |
Minor-Version Drift (0.x semver) |
0.20.x → 0.21.x is a minor bump, which can carry breaking changes under 0.x semantics. The 0.21.1 notes show none, but we did not review 0.21.0. |
Action: review the 0.21.0 changelog before bumping. Pin ^0.21 and gate upgrades behind CI. |
| False Positives/Negatives | Complex Laravel patterns (dynamic data providers) may trigger edge cases. Unchanged. | Run --no-cache in CI to surface issues early; use <exclude-files> for stragglers. |
| Attribute Support Gaps | Laravel-specific attributes (e.g., CreatesApplication) may not be recognized. Unchanged. |
Extend via Psalm's plugin API if needed (low priority — most tests use standard PHPUnit attributes). |
| Performance Overhead | Static analysis adds ~10–30% to test-suite initialization (Psalm baseline). Unchanged. | Cache in CI (--cache-globals); run in parallel with PHPUnit. |
| Breaking Changes | None in 0.21.1. Release is docs, CI bumps, and internal fixture annotations. | Standard changelog review on each minor. |
assertDatabaseHas)? Higher complexity = greater plugin value.Argument 1 of assertSame() must be of type int, string given (e.g., $user->id is string but asserted as int)Data provider must return iterable, array returned (e.g., missing yield)0.x semver, assign explicit ownership of minor-version review.#[Test]) fully supported.- name: Psalm (Tests)
run: ./vendor/bin/psalm --init --no-cache --output-format=github
| Phase | Action | Tools/Commands |
|---|---|---|
| Assessment | Audit Psalm/PHPUnit versions and test complexity. | composer show psalm/plugin-phpunit · grep -r "#[Test]" tests/ |
| Installation | Add plugin to composer.json and enable it. |
composer require --dev psalm/plugin-phpunit:^0.21 · vendor/bin/psalm-plugin enable psalm/plugin-phpunit |
| Configuration | Update psalm.xml to include test files and configure strictness. |
<file-list><dir>app/</dir><dir>tests/</dir></file-list> + plugin-class registration |
| Validation | Run Psalm on a subset of tests (e.g., tests/Unit/) to validate warnings. |
./vendor/bin/psalm --init --no-cache |
| CI Integration | Add Psalm to CI before PHPUnit. | Psalm's published GitHub Actions template |
| Iteration | Fix critical warnings (assertion type mismatches) and refine psalm.xml exclusions. |
Adjust <exclude-files> as needed |
| Upgrade Hygiene (new) | On every 0.x minor bump, read the changelog; patches (e.g., 0.21.1) can be auto-merged if CI is green. |
Dependabot/Renovate with grouped patch updates |
assertDatabaseHas(), assertModelExists().assertJson()/assertJsonStructure() against actual response types.bind()/singleton() assertions.@psalm-suppress for complex generators (yield from).psalm.xml changes. Upgrade is a drop-in replacement for 0.20.x.tests/Unit/ first — lower complexity, higher ROI. Example: catch assertSame($user->id, 1) where $user->id is string.0.x minor changelogs, and (c) maintaining the baseline file.--cache-globals and shard by directory (tests/Unit, tests/Feature) as the suite grows.exclude-files size as a hygiene metric.| Failure | Symptom | Response |
|---|---|---|
| CI latency creep | Pre-test analysis stage exceeds PR feedback budget | Enable caching; shard by directory; parallelize with PHPUnit |
| Warning fatigue | Team starts blanket-suppressing or excluding tests | Enforce baseline-gated "new issues only"; review suppressions in code review |
| Silent vendor drift | 0.x minor bump introduces behavior change unseen |
Pin ^0.21; require changelog review on minors; auto-merge patches only |
| False positives on dynamic providers | Builds fail on valid Laravel test patterns | Targeted @psalm-suppress with a comment; escalate upstream if recurring |
| Champion leaves | Warnings accumulate, baseline grows, gate disabled | Document the process in the repo; keep the workflow in CI config, not tribal knowledge |
tests/Unit/; (3) walk through 2–3 real warnings with the team; (4) enable the baseline-gated CI check.How can I help you explore Laravel packages today?