PHPackages                             mrpunyapal/rector-pest - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. mrpunyapal/rector-pest

ActiveLibrary[Testing &amp; Quality](/categories/testing)

mrpunyapal/rector-pest
======================

Rector upgrade rules for Pest - refactoring and best practices for Pest testing framework

0.2.15(1mo ago)6977.7k↑148.1%620MITPHPPHP ^8.2CI passing

Since Dec 3Pushed 2d ago4 watchersCompare

[ Source](https://github.com/MrPunyapal/rector-pest)[ Packagist](https://packagist.org/packages/mrpunyapal/rector-pest)[ GitHub Sponsors](https://github.com/mrpunyapal)[ RSS](/packages/mrpunyapal-rector-pest/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (10)Dependencies (22)Versions (35)Used By (20)

Rector Pest
===========

[](#rector-pest)

[![Latest Version on Packagist](https://camo.githubusercontent.com/31bc6952e11217269533c59f73b916e647db603dd6506de94c6246f71bde2b3f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d7270756e796170616c2f726563746f722d706573742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mrpunyapal/rector-pest)[![Total Downloads on Packagist](https://camo.githubusercontent.com/a7a893ab63c76181d32ac18366f59bc23c4f733669ad53a4d2a98581b6b3937a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d7270756e796170616c2f726563746f722d706573742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mrpunyapal/rector-pest)[![CI](https://github.com/mrpunyapal/rector-pest/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mrpunyapal/rector-pest/actions/workflows/ci.yml)

Rector rules for [PestPHP](https://pestphp.com/) to improve code quality and help with version upgrades.

Rector Pest now also exposes a semantic remediation layer for safe Pest-specific diagnostics. Canonical issue identifiers, semantic metadata, and diagnostic-to-fix mapping live inside this package so Rector rules can stay independent from any single analyzer while still remaining ready for future PestStan interoperability.

Available Rules
---------------

[](#available-rules)

See all available Pest rules [here](/docs/rules.md). See the semantic architecture and interoperability contract [here](/docs/semantic-architecture.md).

Installation
------------

[](#installation)

```
composer require --dev mrpunyapal/rector-pest
```

Available Rule Sets
-------------------

[](#available-rule-sets)

### Code Quality

[](#code-quality)

Improve your Pest tests with better readability and expressiveness.

The code-quality set also fixes a small set of PestStan-aligned anti-patterns, including static Pest callbacks that actually require instance binding, invalid `beforeAll()`/`afterAll()` usage inside `describe()` blocks, invalid literal `repeat()` counts, and redundant literal type expectations when another matcher keeps the chain meaningful. Empty test closures and impossible literal/type combinations remain modeled in the semantic registry, but they are not auto-fixed when the runtime semantics would change or the intent would become ambiguous.

These semantic fixes do not require PestStan at runtime for package consumers. This repository still keeps PestStan as a development-only dependency for its own PHPStan configuration, while Rector Pest itself owns the canonical issue registry and consumes analyzer diagnostics through stable identifiers instead of direct analyzer coupling.

```
// rector.php
use RectorPest\Set\PestSetList;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/tests',
    ])
    ->withSets([
        PestSetList::PEST_CODE_QUALITY,
    ]);
```

SetDescription[`PestSetList::PEST_CODE_QUALITY`](config/sets/pest-code-quality.php)Converts expect() assertions to use Pest's built-in matchers for better readability[`PestSetList::PEST_CHAIN`](config/sets/pest-chain.php)Merges multiple expect() calls into chained expectations and optimizes their order.[`PestSetList::PEST_LARAVEL`](config/sets/pest-laravel.php)Laravel-specific rules (requires `illuminate/support`): converts `Str::` equality checks to Pest string case matchers[`PestSetList::PEST_MIGRATION`](config/sets/pest-migration.php)PHPUnit → Pest migration rules (opt-in): converts assertions, data providers, and test structure[`PestSetList::PEST_BROWSER`](config/sets/pest-browser.php)Pest Browser code-quality rules (requires `pestphp/pest-plugin-browser`): converts `expect($page->getter())` patterns to dedicated browser assertion methods### Version Upgrade Sets

[](#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,
    ]);
```

SetDescription`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

[](#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
    ]);
```

SetDescription[`PestSetList::PEST_30`](config/sets/pest30.php)Pest v2 → v3 migration rules[`PestSetList::PEST_40`](config/sets/pest40.php)Pest v3 → v4 migration rulesChaining Expectations
---------------------

[](#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 as `expect()`
- 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/rector` versions older than 2.4.1, chaining still works but all method calls are printed inline on a single line.

PHPUnit to Pest Migration
-------------------------

[](#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:**

RuleDescription`ConvertAssertToExpectRector`Converts `$this->assert*()` calls to `expect()->` chains`ConvertExpectExceptionToThrowRector`Converts `$this->expectException*()` plus the throwing call to `expect(fn() => ...)->toThrow()`Pest Browser Testing
--------------------

[](#pest-browser-testing)

The `PEST_BROWSER` set improves code quality of tests written with [`pestphp/pest-plugin-browser`](https://pestphp.com/docs/browser-testing). 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-browser` installed.

```
// rector.php
use RectorPest\Set\PestSetList;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/tests/Browser',
    ])
    ->withSets([
        PestSetList::PEST_BROWSER,
    ]);
```

**Included rules:**

RuleTransforms`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 `assertUrlIs` is covered because it is the only URL-related assertion that has a direct `expect($page->getter())->toBe()` equivalent. Path, scheme, host, port, query-string, and fragment assertions (`assertPathIs`, `assertSchemeIs`, `assertHostIs`, etc.) have no `expect()` counterparts in the plugin and are out of scope.

> **Aria/data attribute assertions scope:** `assertAriaAttribute` and `assertDataAttribute` are covered by `UseBrowserAriaAndDataAttributeAssertionsRector`. Note that the plugin methods accept the attribute name *without* the `aria-`/`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('Welcome');
expect($page->content())->not->toContain('');
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('Welcome');
$page->assertSourceMissing('');
$page->assertScript('document.title', 'Home Page');
$page->assertScript('window.scrollY', 0);
$page->assertUrlIs('https://example.com/home');
```

Using Individual Rules
----------------------

[](#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
--------------

[](#running-rector)

```
# Preview changes
vendor/bin/rector process --dry-run

# Apply changes
vendor/bin/rector process
```

Semantic Interoperability
-------------------------

[](#semantic-interoperability)

Rector Pest separates three concerns:

- deterministic semantic analyzers that only return local facts needed for a safe transformation
- a canonical issue registry with stable identifiers and metadata for interoperability
- an interop layer that maps external diagnostics onto canonical issues and safe Rector fixes

That split keeps the transformation layer conservative. For example, redundant literal type cleanup only removes checks when the literal value is deterministic and the surrounding chain does not branch or transform the expectation subject. Static callback cleanup also distinguishes nested Pest callbacks from nested non-Pest closure trees so outer `describe()` callbacks are not rewritten just because an inner hook needs instance binding.

The current semantic contract is documented in [docs/semantic-architecture.md](/docs/semantic-architecture.md).

Requirements
------------

[](#requirements)

- PHP 8.2+
- Rector 2.0+

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

###  Health Score

57

—

FairBetter than 98% of packages

Maintenance95

Actively maintained with recent releases

Popularity47

Moderate usage in the ecosystem

Community31

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 99% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

32

Last Release

49d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/230c58a4f918ca3e3f2988b38721230698bce88f76ae9087e4377ba0b3a074d5?d=identicon)[MrPunyapal](/maintainers/MrPunyapal)

---

Top Contributors

[![MrPunyapal](https://avatars.githubusercontent.com/u/53343069?v=4)](https://github.com/MrPunyapal "MrPunyapal (412 commits)")[![faissaloux](https://avatars.githubusercontent.com/u/60013703?v=4)](https://github.com/faissaloux "faissaloux (2 commits)")[![guanguans](https://avatars.githubusercontent.com/u/22309277?v=4)](https://github.com/guanguans "guanguans (1 commits)")[![raphaelstolt](https://avatars.githubusercontent.com/u/48225?v=4)](https://github.com/raphaelstolt "raphaelstolt (1 commits)")

---

Tags

pestrectorrectorphptesttestingphpunitpestdevstatic analysisrefactoringastrectorpestphpinstant-upgrades

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mrpunyapal-rector-pest/health.svg)

```
[![Health](https://phpackages.com/badges/mrpunyapal-rector-pest/health.svg)](https://phpackages.com/packages/mrpunyapal-rector-pest)
```

###  Alternatives

[laravel/pao

Agent-optimized output for PHP testing tools

6523.7M140](/packages/laravel-pao)[ssch/typo3-rector

Instant fixes for your TYPO3 PHP code by using Rector.

2603.2M436](/packages/ssch-typo3-rector)[savinmikhail/add_named_arguments_rector

Rector rule to add names to arguments for functions'/methods' calls

19100.7k5](/packages/savinmikhail-add-named-arguments-rector)[donatj/mock-webserver

Simple mock web server for unit testing

1402.7M97](/packages/donatj-mock-webserver)[guanguans/laravel-soar

SQL optimizer and rewriter for laravel. - laravel 的 SQL 优化器和重写器。

2248.4k](/packages/guanguans-laravel-soar)[mrpunyapal/peststan

PHPStan extension for Pest PHP testing framework

4983.7k23](/packages/mrpunyapal-peststan)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
