mrpunyapal / peststan
PHPStan extension for Pest PHP testing framework
Fund package maintenance!
Requires
- php: ^8.2
- phpstan/phpstan: ^2.0
Requires (Dev)
- laravel/pint: ^1.18
- pestphp/pest: ^3.0 || ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.0
README
PHPStan extension for Pest PHP testing framework. Provides type-safe expectations, proper $this binding in test closures, and accurate return types for all Pest functions.
Requirements
- PHP ^8.2
- PHPStan ^2.0
- Pest PHP ^3.0 or ^4.0
Installation
composer require --dev mrpunyapal/peststan
If you have phpstan/extension-installer (recommended), the extension is registered automatically.
Otherwise, add it manually to your phpstan.neon or phpstan.neon.dist:
includes: - vendor/mrpunyapal/peststan/extension.neon
Features
Generic expect() Function
The extension provides generic type inference for Pest's expect() function, so PHPStan knows the exact type of the expectation value:
expect('hello'); // Expectation<string> expect(42); // Expectation<int> expect(['a' => 1]); // Expectation<array{a: int}> expect($user); // Expectation<User> expect(); // Expectation<null>
Type Narrowing Assertions
Type-checking assertion methods narrow the generic type parameter, so PHPStan tracks the type through assertion chains:
/** @var int|string $value */ $value = getValue(); expect($value)->toBeString(); // PHPStan now knows the expectation wraps a string expect($value)->toBeInstanceOf(User::class); // PHPStan now knows the expectation wraps a User
Supported type-narrowing assertions:
toBeString, toBeInt, toBeFloat, toBeBool, toBeArray, toBeList, toBeObject, toBeCallable, toBeIterable, toBeNumeric, toBeScalar, toBeResource, toBeTrue, toBeFalse, toBeNull, toBeInstanceOf.
Type-Safe and() Chaining
The and() method properly changes the generic type parameter, enabling type-safe assertion chains:
expect('hello') ->toBeString() // Expectation<string> ->and(42) // Expectation<int> ->toBeInt() // Expectation<int> ->and(['a', 'b']) // Expectation<array{string, string}> ->toHaveCount(2); // Expectation<array{string, string}>
$this Binding in Test Closures
The extension ensures $this is properly typed as PHPUnit\Framework\TestCase inside all Pest test closures and lifecycle hooks:
it('can access test case methods', function () { $this->markTestSkipped(); // PHPStan knows $this is TestCase }); beforeEach(function () { $this->assertTrue(true); // Works in hooks too });
Supported functions: it(), test(), describe(), beforeEach(), afterEach(), beforeAll(), afterAll().
Pest Function Return Types
Accurate return types for all Pest global functions:
| Function | Return Type |
|---|---|
expect($value) |
Expectation<TValue> |
it() / test() / todo() |
TestCall |
describe() |
DescribeCall |
not() and each() Return Types
expect('hello')->not(); // OppositeExpectation<string> expect([1, 2])->each(); // EachExpectation<array{int, int}>
TestCall Chaining
All TestCall methods are properly typed for fluent chaining:
it('does something', function () { /* ... */ }) ->with(['a', 'b']) ->group('unit', 'feature') ->skip(false) ->depends('other test') ->throws(RuntimeException::class) ->repeat(3);
Architecture Testing Support
Architecture testing methods are fully supported:
expect('App\Models') ->toExtend('Illuminate\Database\Eloquent\Model') ->ignoring('App\Models\Legacy'); expect('App') ->classes() ->toBeFinal(); expect('App\Actions')->toBeInvokable(); expect('App\DTOs')->toBeReadonly(); expect('App')->toUseStrictTypes();
Testing
composer test # Run all checks (lint + types + unit) composer lint # Apply code style fixes (Rector + Pint) composer test:lint # Check code style (dry-run) composer test:types # Run PHPStan analysis composer test:unit # Run Pest unit tests
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License. See LICENSE for more information.