pestphp/pest-plugin-rector
The official Pest plugin for Rector that automates test code migrations and upgrades. Convert PHPUnit tests to Pest, upgrade Pest 1 to 2/3, and apply custom Rector rules through a single, easy-to-configure ruleset—keeping your test suite clean and modern.
Architecture fit. This is a dev-toolchain artifact, not an application dependency. It ships as a Rector rule set that rewrites test source files at migration time — no service provider, no facade, no publishable config, no runtime entry into the application graph. For a TPM this is the most favorable risk envelope on offer: the blast radius is bounded to a migration branch, developer laptops, and CI. The package is only defensible when two conditions hold at once: the organization has a committed Pest standardization intent, and the legacy PHPUnit suite is large enough that hand-conversion cost dominates the cost of pinning and babysitting a low-adoption tool. Absent either, the value is zero and evaluation should stop.
Integration feasibility. Wiring is trivial and can be proven in an afternoon: install as --dev alongside rector/rector, author a rector.php scoped to a single test file, load the Pest set list, and run vendor/bin/rector process --dry-run. The TPM-level feasibility question is not installation — it is coverage against our idioms. Pest migration is not syntax-only: data providers, setUp/tearDown → beforeEach/afterEach, @depends ordering, mocking (createMock, partial mocks), and custom base-class helpers all carry semantics the rules may not model. The honest framing to stakeholders is a 70/30 split: the plugin clears the boring mechanical majority and leaves a manual tail that needs senior attention. Feasibility should be signed off on a measured automated-vs-manual ratio from a representative repo, not on the README.
Technical risk. Three risks dominate, in order:
withPaths() per module, one PR per directory), the review gate becomes ceremonial and the semantic-drift risk goes unmitigated.Key questions to resolve before commitment.
src/Set/ actually support it?Stack fit. Laravel/PHP services running PHPUnit today, with any existing Rector pipeline, are the natural fit. The plugin extends an existing quality gate rather than introducing a new category of tooling — that is the cheapest possible adoption shape. Repos with no Rector footprint and no Pest intent should be excluded from scope entirely.
Migration path. The credible path is a time-boxed pilot on one representative repository, in this sequence:
--dev, pin the plugin, rector/rector, and pestphp/pest together.vendor/pestphp/pest-plugin-rector/src/Set/ — do not trust the README or any doc; read the vendor source../vendor/bin/pest.withPaths() one subdirectory at a time, one PR per directory.Compatibility and coexistence. Use Pest's own interactive migration for project plumbing (phpunit.xml, bootstrap) and Rector for the mechanical AST rewrites of test bodies — but on separate branches, merged deliberately. Do not let both tools touch the same file in one commit; the conflict resolution cost exceeds the automation gain. Where the Pest set is too broad, drop withSets() and enable individual rule classes via ->withRules([...]), reading each class first since the class name states the exact transformation. Order sets deliberately: PHPUnit/PHP-version sets before the Pest set, so canonical PHPUnit syntax is normalized first.
Sequencing against other work. This should not run concurrently with a framework upgrade, a PHP version bump, or a CI re-platforming. The plugin's value is highest when treated as an isolated, measurable engineering-enablement workstream. If multiple teams face the same PHPUnit→Pest conversion, centralize the pilot, publish the measured ratio, and let teams consume the result rather than repeating the evaluation N times.
Decision gates. Gate 1: does the rule set transform a representative sample cleanly enough that manual cleanup is bounded? Gate 2: does coverage hold after transformation on the pilot module? Gate 3: is there a named owner and a pinning policy? Fail any gate and the correct answer is defer.
Maintenance. This is a vendored, pinned, owned dependency — not a supported product. There is no vendor SLA, thin issue history, and a contributor bus factor near one. Budget explicitly for: version pinning across the plugin/Rector/Pest triple, validation on a scratch branch before any bump, and a standing allowance for forking or authoring a custom rule when coverage gaps appear. The runbook should name the owner and the fork path before the pilot starts, not after the first breakage.
Support. Internal-only. Developers hitting a failed or nonsensical transformation will have no upstream to escalate to. Practical support model: the owner triages, the team falls back to individual rules or hand-conversion, and problematic rule classes get skipped via ->withSkip() or excluded from withRules().
Scaling. The tool scales badly in review even where it scales well in execution. Executing Rector across a large suite is fast; reviewing the output is the actual bottleneck and does not compress. Plan capacity around diff review and manual-tail cleanup, not around runtime. The other scaling constraint is CI: adding a dry-run step is cheap, but making it a real gate (nonzero exit when changes exist) introduces a new class of red builds that someone must own.
Failure modes to plan for.
withPaths() follows globs, not intent. Point at tests/Feature and tests/Unit explicitly, never the repo root, and ->withSkip() fixture/snapshot files that merely look like tests.Ramp-up. Low technical ramp-up, moderate organizational ramp-up. Any developer can run a dry-run in minutes; the harder part is instilling the discipline — dry-run first, one directory per PR, review every diff even when green, compare counts and coverage against the PHPUnit baseline. Recommend a single short internal write-up covering the gotchas (set ordering, path scoping, idempotency check, manual-tail expectations) rather than relying on each developer reading vendor source.
Executive framing. A free, MIT-licensed, developer-only utility that automates a mechanical slice of test modernization, adds no production dependency, and bounds downside to engineering time. Fund a time-boxed pilot, measure the automated-vs-manual ratio and any coverage drift, then decide the broader migration on our own data.
How can I help you explore Laravel packages today?