mrpunyapal/peststan
PHPStan extension for Pest PHP. Adds generic typing for expect(), type-narrowing assertions, type-safe and() chaining, correct $this binding in test closures, and accurate return types for Pest functions. Supports Pest 3–5 on PHP 8.2+.
Installation:
composer require --dev mrpunyapal/peststan
If using phpstan/extension-installer, no further config is needed. Otherwise, add to phpstan.neon:
includes:
- vendor/mrpunyapal/peststan/extension.neon
First Use Case: Run PHPStan on your test directory:
vendor/bin/phpstan analyse tests
PestStan will now provide type hints for expect() calls and $this binding in test closures.
Expectation Chaining:
expect($user)
->toBeInstanceOf(User::class)
->and($user->posts)
->toHaveCount(3);
User) through assertions.Dynamic Property Inference:
beforeEach(function () {
$this->user = User::factory()->create(); // PestStan infers type as `User`
});
TestCase Integration:
// tests/Pest.php
uses(Tests\TestCase::class)->in('Feature');
// tests/Feature/UserTest.php
it('uses TestCase methods', function () {
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
// PHPStan knows $this is Tests\TestCase
});
Custom TestCase Helpers:
// tests/Pest.php
uses(CustomTestCase::class)->in('Unit');
// CustomTestCase.php
public function customHelper() { ... }
// Test file
it('uses custom helper')->customHelper(); // Works in TestCall chains
Architecture Testing:
expect('App\Models')
->toExtend('Illuminate\Database\Eloquent\Model')
->ignoring('App\Models\Legacy');
Rule-Based Refactoring: Use PestStan’s static analysis rules to catch issues early:
// Catches empty test closures
it('forgot assertions'); // Triggers `pest.test.emptyClosure`
$this in beforeAll():
beforeAll(function () {
$this->db = new Database(); // ❌ Error: $this unavailable
});
Fix: Use beforeEach() instead.
Union Types in Dynamic Properties:
beforeEach(function () {
$this->item = new Post(); // Type: Post
});
beforeEach(function () {
$this->item = new Comment(); // Type: Post|Comment
});
Tip: PestStan unionizes types across hooks.
Static Closures:
it('fails', static function () { ... }); // ❌ Error: `pest.test.staticClosure`
Manual TestCase Override:
If auto-detection fails, force it in phpstan.neon:
parameters:
peststan:
testCaseClass: App\Testing\CustomTestCase
Ignoring Rules: Suppress specific rules inline:
/** @phpstan-ignore pest.expectation.impossible */
expect(42)->toBeString(); // Ignores redundant assertion
Extension Paths:
Ensure Pest.php files are in PHPStan’s paths:
parameters:
peststan:
pestConfigFiles:
- tests/Pest.php
Custom Assertions:
Extend ExpectationTypeNarrower to support new expect() methods.
Rule Customization:
Add new static analysis rules by extending PestDiagnosticIdentifiers.
Pest Version Compatibility:
Check src/Analysis/PestVersionResolver.php to ensure support for your Pest version.
```markdown
### Pro Tips for Daily Use
- **Leverage `and()` for Complex Chains**:
```php
expect($user)
->toBeInstanceOf(User::class)
->and($user->posts)
->toHaveCount(3)
->each(fn ($post) => expect($post)->toBePublished());
PHPStan tracks types through each step.
Use toBeInstanceOf for Polymorphism:
expect($entity)->toBeInstanceOf(Post::class | Comment::class);
PestStan handles union types in assertions.
Architecture Testing: Validate class relationships without runtime checks:
expect('App\Services')->toBeInvokable();
Rule-Based CI Checks: Add PestStan rules to your CI baseline to enforce test quality:
# phpstan.neon
parameters:
level: 8
ignoreErrors:
- identifier: pest.test.emptyClosure
How can I help you explore Laravel packages today?