mrpunyapal / rector-pest
Rector upgrade rules for Pest - refactoring and best practices for Pest testing framework
Package info
github.com/MrPunyapal/rector-pest
Type:rector-extension
pkg:composer/mrpunyapal/rector-pest
Fund package maintenance!
Requires
- php: ^8.2
- mrpunyapal/peststan: ^0.2.5
- rector/rector: ^2.0
- symplify/rule-doc-generator-contracts: ^11.2
Requires (Dev)
- laravel/pint: ^1.18
- nunomaduro/pao: ^0.1.4
- pestphp/pest: ^3.0 || ^4.0
- phpstan/phpstan: ^2.1
- symplify/rule-doc-generator: ^12.2
README
Rector rules for PestPHP to improve code quality and help with version upgrades.
Available Rules
See all available Pest rules here.
Installation
composer require --dev mrpunyapal/rector-pest
Available Rule Sets
Code Quality
Improve your Pest tests with better readability and expressiveness.
// rector.php use RectorPest\Set\PestSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withSets([ PestSetList::PEST_CODE_QUALITY, ]);
| Set | Description |
|---|---|
PestSetList::PEST_CODE_QUALITY |
Converts expect() assertions to use Pest's built-in matchers for better readability |
PestSetList::PEST_CHAIN |
Merges multiple expect() calls into chained expectations and optimizes their order. |
PestSetList::PEST_LARAVEL |
Laravel-specific rules (requires illuminate/support): converts Str:: equality checks to Pest string case matchers |
PestSetList::PEST_MIGRATION |
PHPUnit → Pest migration rules (opt-in): converts assertions, data providers, and test structure |
PestSetList::PEST_BROWSER |
Pest Browser code-quality rules (requires pestphp/pest-plugin-browser): converts expect($page->getter()) patterns to dedicated browser assertion methods |
Version Upgrade Sets
Use PestLevelSetList to automatically upgrade to a specific Pest version. Sets for higher versions include sets for lower versions.
// rector.php use RectorPest\Set\PestLevelSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withSets([ PestLevelSetList::UP_TO_PEST_40, ]);
| Set | Description |
|---|---|
PestLevelSetList::UP_TO_PEST_30 |
Upgrade from Pest v2 to v3 |
PestLevelSetList::UP_TO_PEST_40 |
Upgrade from Pest v2/v3 to v4 (includes v3 changes) |
Manual Version Configuration
Use PestSetList if you only want changes for a specific version:
// rector.php use RectorPest\Set\PestSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withSets([ PestSetList::PEST_30, // Only v2→v3 changes ]);
| Set | Description |
|---|---|
PestSetList::PEST_30 |
Pest v2 → v3 migration rules |
PestSetList::PEST_40 |
Pest v3 → v4 migration rules |
Chaining Expectations
The PEST_CHAIN set automatically merges multiple expect() calls into a single chained expression.
// rector.php use RectorPest\Set\PestSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withSets([ PestSetList::PEST_CODE_QUALITY, PestSetList::PEST_CHAIN, ]);
Before:
expect($value1)->toBe(10); expect($value1)->toBeInt(); expect($value2)->toBe(20); expect($value2)->toBeString(); expect($value3)->toBe(30);
After:
expect($value1)->toBe(10) ->toBeInt() ->and($value2)->toBe(20) ->toBeString() ->and($value3)->toBe(30);
Formatting rules (requires rector/rector 2.4.1+):
- The first matcher after
expect()stays on the same line asexpect() - The first matcher after
->and()stays on the same line as->and() - Every additional matcher in a segment goes on its own indented line
->not->toBeX()is treated as a single unit and stays inline
Note: On
rector/rectorversions older than 2.4.1, chaining still works but all method calls are printed inline on a single line.
PHPUnit to Pest Migration
The PEST_MIGRATION set helps convert PHPUnit test patterns to Pest equivalents. This is an opt-in set — review changes carefully after applying.
// rector.php use RectorPest\Set\PestSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withSets([ PestSetList::PEST_MIGRATION, ]);
Included rules:
| Rule | Description |
|---|---|
ConvertAssertToExpectRector |
Converts $this->assert*() calls to expect()-> chains |
ConvertExpectExceptionToThrowRector |
Converts $this->expectException*() plus the throwing call to expect(fn() => ...)->toThrow() |
Pest Browser Testing
The PEST_BROWSER set improves code quality of tests written with pestphp/pest-plugin-browser. It converts verbose expect($page->getter()) patterns into the plugin's dedicated browser assertion methods, producing clearer failure messages and more readable tests.
Requirement: The target project must have
pestphp/pest-plugin-browserinstalled.
// rector.php use RectorPest\Set\PestSetList; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests/Browser', ]) ->withSets([ PestSetList::PEST_BROWSER, ]);
Included rules:
| Rule | Transforms |
|---|---|
UseBrowserValueAssertionsRector |
expect($page->value($sel))->toBe($v) → $page->assertValue($sel, $v) and negated form → assertValueIsNot |
UseBrowserAriaAndDataAttributeAssertionsRector |
expect($page->attribute($sel, 'aria-*'))->toBe($v) → $page->assertAriaAttribute($sel, $attr, $v) and data-* form → assertDataAttribute |
UseBrowserAttributeAssertionsRector |
expect($page->attribute($sel, $attr))->toBe/toContain/not->toContain/toBeNull → assertAttribute, assertAttributeContains, assertAttributeDoesntContain, assertAttributeMissing |
UseBrowserSourceAssertionsRector |
expect($page->content())->toContain($html) → assertSourceHas and negated form → assertSourceMissing |
UseBrowserScriptAssertionsRector |
expect($page->script($expr))->toBe/toEqual($v) → $page->assertScript($expr, $v) |
UseBrowserUrlAssertionsRector |
expect($page->url())->toBe($url) → $page->assertUrlIs($url) |
URL assertions scope: only
assertUrlIsis covered because it is the only URL-related assertion that has a directexpect($page->getter())->toBe()equivalent. Path, scheme, host, port, query-string, and fragment assertions (assertPathIs,assertSchemeIs,assertHostIs, etc.) have noexpect()counterparts in the plugin and are out of scope.
Aria/data attribute assertions scope:
assertAriaAttributeandassertDataAttributeare covered byUseBrowserAriaAndDataAttributeAssertionsRector. Note that the plugin methods accept the attribute name without thearia-/data-prefix — the rule strips the prefix automatically.
Before:
expect($page->value('input[name=email]'))->toBe('test@example.com'); expect($page->attribute('button', 'aria-label'))->toBe('Close'); expect($page->attribute('div', 'data-id'))->toBe('123'); expect($page->attribute('img', 'alt'))->toBe('Profile Picture'); expect($page->attribute('div', 'class'))->toContain('container'); expect($page->attribute('div', 'class'))->not->toContain('hidden'); expect($page->attribute('button', 'disabled'))->toBeNull(); expect($page->content())->toContain('<h1>Welcome</h1>'); expect($page->content())->not->toContain('<div class="error">'); expect($page->script('document.title'))->toBe('Home Page'); expect($page->script('window.scrollY'))->toEqual(0); expect($page->url())->toBe('https://example.com/home');
After:
$page->assertValue('input[name=email]', 'test@example.com'); $page->assertAriaAttribute('button', 'label', 'Close'); $page->assertDataAttribute('div', 'id', '123'); $page->assertAttribute('img', 'alt', 'Profile Picture'); $page->assertAttributeContains('div', 'class', 'container'); $page->assertAttributeDoesntContain('div', 'class', 'hidden'); $page->assertAttributeMissing('button', 'disabled'); $page->assertSourceHas('<h1>Welcome</h1>'); $page->assertSourceMissing('<div class="error">'); $page->assertScript('document.title', 'Home Page'); $page->assertScript('window.scrollY', 0); $page->assertUrlIs('https://example.com/home');
Using Individual Rules
You can also use individual rules instead of sets:
// rector.php use RectorPest\Rules\ChainExpectCallsRector; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withPaths([ __DIR__ . '/tests', ]) ->withRules([ ChainExpectCallsRector::class, ]);
Running Rector
# Preview changes vendor/bin/rector process --dry-run # Apply changes vendor/bin/rector process
Requirements
- PHP 8.2+
- Rector 2.0+
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
The MIT License (MIT). Please see License File for more information.