Download the PHP package helgesverre/pest-to-phpunit without Composer
On this page you can find all versions of the php package helgesverre/pest-to-phpunit. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download helgesverre/pest-to-phpunit
More information about helgesverre/pest-to-phpunit
Files in helgesverre/pest-to-phpunit
Package pest-to-phpunit
Short Description Rector rules to convert Pest tests to PHPUnit
License MIT
Homepage https://github.com/HelgeSverre/pest-to-phpunit
Informations about the package pest-to-phpunit
Pest to PHPUnit — Rector Extension
[!WARNING] This project is experimental. It handles many common Pest patterns, but edge cases may produce incorrect output. Always review the generated code before committing.
A Rector extension that automatically converts Pest test files into PHPUnit test classes.
Handles test() / it() blocks, hooks, datasets, expect() assertion chains, modifiers, and more — getting you most of the way there automatically while leaving clear TODO markers for anything that needs manual review.
Installation
Usage
1. Add the set to your rector.php
Namespace inference (recommended)
Pest test files typically have no namespace declaration. If your project uses PSR-4 autoloading for tests (e.g. "Tests\\": "tests/" in composer.json), enable namespace inference to automatically add the correct namespace to generated classes:
This reads your composer.json autoload-dev and autoload PSR-4 mappings to derive the namespace from the file path (e.g. tests/Feature/ExampleTest.php → namespace Tests\Feature;).
2. Run Rector
3. Review TODO comments
Some Pest features cannot be auto-converted and will be marked with // TODO(Pest): comments. After running Rector, search for these to find anything that needs manual attention:
Examples
Basic test() / it()
Before:
After:
describe() blocks
Hooks → setUp / tearDown
uses() → extends + traits
Modifiers: skip(), todo(), throws(), group()
expect()->toThrow()
Pest Faker Plugin
The use function Pest\Faker\fake; import is automatically removed. $this->faker via the WithFaker trait works naturally through trait conversion (see uses() above).
Datasets → #[DataProvider]
Feature Support
Core Constructs
| Pest Feature | Status | PHPUnit Output |
|---|---|---|
test() / it() |
✅ | public function test_*(): void |
describe() (nested, 4+ levels deep) |
✅ | Method name prefixing |
beforeEach / afterEach |
✅ | setUp() / tearDown() |
beforeAll / afterAll |
✅ | setUpBeforeClass() / tearDownAfterClass() |
uses(TestCase::class) |
✅ | extends TestCase |
uses(Trait::class) |
✅ | use Trait; |
covers(Foo::class) |
✅ | #[CoversClass(Foo::class)] |
coversNothing() |
✅ | #[CoversNothing] |
dataset('name', [...]) |
✅ | Static data provider method |
dataset('name', fn() => ...) |
✅ | Generator-based provider |
Describe-scoped beforeEach/afterEach |
✅ | Inlined into test methods (try/finally for afterEach) |
| Non-Pest code preserved | ✅ | Kept alongside generated class |
Test Modifiers
| Modifier | Status | PHPUnit Output |
|---|---|---|
->skip('reason') |
✅ | $this->markTestSkipped(...) |
->skip($condition, 'reason') |
✅ | Conditional if + markTestSkipped |
->todo() |
✅ | $this->markTestIncomplete('TODO') |
->group('name') |
✅ | #[Group('name')] |
->depends('test') |
✅ | #[Depends('test_*')] |
->covers(Foo::class) |
✅ | #[CoversClass(Foo::class)] |
->with('dataset') |
✅ | #[DataProvider('dataset')] |
->with([...]) |
✅ | Inline provider method + #[DataProvider] |
Multiple ->with() (cross-join) |
✅ | Composed cross-join provider method |
->throws(Exception::class) |
✅ | expectException + expectExceptionMessage |
->after(fn() => ...) |
✅ | Test body wrapped in try/finally |
->repeat(N) |
✅ | for loop wrapping test body |
->only() |
✅ | #[Group('only')] |
expect() Assertions
Type Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBeString / toBeInt / toBeFloat / toBeArray |
✅ | assertIsString / assertIsInt / assertIsFloat / assertIsArray |
toBeBool / toBeCallable / toBeIterable |
✅ | assertIsBool / assertIsCallable / assertIsIterable |
toBeNumeric / toBeObject / toBeResource / toBeScalar |
✅ | assertIsNumeric / assertIsObject / assertIsResource / assertIsScalar |
toBeInstanceOf |
✅ | assertInstanceOf |
Value Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBe |
✅ | assertSame |
toEqual |
✅ | assertEquals |
toBeTrue / toBeFalse |
✅ | assertTrue / assertFalse |
toBeTruthy / toBeFalsy |
✅ | assertNotEmpty / assertEmpty |
toBeNull |
✅ | assertNull |
toBeEmpty |
✅ | assertEmpty |
toBeJson |
✅ | assertJson |
toBeNan / toBeFinite / toBeInfinite |
✅ | assertNan / assertIsFinite / assertIsInfinite |
Comparison Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBeGreaterThan / toBeLessThan |
✅ | assertGreaterThan / assertLessThan |
toBeGreaterThanOrEqual / toBeLessThanOrEqual |
✅ | assertGreaterThanOrEqual / assertLessThanOrEqual |
toBeBetween($min, $max) |
✅ | assertGreaterThanOrEqual + assertLessThanOrEqual |
toEqualWithDelta |
✅ | assertEqualsWithDelta |
toEqualCanonicalizing |
✅ | assertEqualsCanonicalizing |
String Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toStartWith / toEndWith |
✅ | assertStringStartsWith / assertStringEndsWith |
toMatch |
✅ | assertMatchesRegularExpression |
toContain (string subject) |
✅ | assertStringContainsString |
toContain (array subject) |
✅ | assertContains |
toContain($a, $b, $c) (multi-arg) |
✅ | Multiple assertContains calls |
Array / Collection Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toHaveCount |
✅ | assertCount |
toHaveLength |
✅ | assertSame($n, is_string($x) ? strlen($x) : count($x)) |
toHaveKey |
✅ | assertArrayHasKey |
toHaveKeys(['a', 'b']) |
✅ | Multiple assertArrayHasKey calls |
toContainEqual |
✅ | assertContainsEquals |
toContainOnlyInstancesOf |
✅ | assertContainsOnlyInstancesOf |
toHaveSameSize |
✅ | assertSameSize |
toBeList |
✅ | assertIsList |
toBeIn([$a, $b]) |
✅ | assertContains($subject, $haystack) |
toMatchArray / toMatchObject |
✅ | assertEquals |
Object Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toHaveProperty('name') |
✅ | assertObjectHasProperty |
toHaveProperties(['a', 'b']) |
✅ | Multiple assertObjectHasProperty calls |
toHaveProperties(['name' => 'John']) |
✅ | assertSame per key-value pair |
toHaveMethod('foo') |
✅ | assertTrue(method_exists(...)) |
toMatchConstraint($c) |
✅ | assertThat($subject, $constraint) |
File / Directory Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBeFile / toBeDirectory |
✅ | assertFileExists / assertDirectoryExists |
toBeReadableFile / toBeWritableFile |
✅ | assertFileIsReadable / assertFileIsWritable |
toBeReadableDirectory / toBeWritableDirectory |
✅ | assertDirectoryIsReadable / assertDirectoryIsWritable |
String Format Assertions (via regex)
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBeUppercase / toBeLowercase |
✅ | assertSame(strtoupper($x), $x) / assertSame(strtolower($x), $x) |
toBeAlpha / toBeAlphaNumeric / toBeDigits |
✅ | assertMatchesRegularExpression |
toBeSnakeCase / toBeKebabCase / toBeCamelCase / toBeStudlyCase |
✅ | assertMatchesRegularExpression |
toBeUuid / toBeUrl |
✅ | assertMatchesRegularExpression |
Array Key Format Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toHaveSnakeCaseKeys / toHaveKebabCaseKeys |
✅ | foreach (array_keys(...)) + regex assert |
toHaveCamelCaseKeys / toHaveStudlyCaseKeys |
✅ | foreach (array_keys(...)) + regex assert |
Exception Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toThrow(Exception::class) |
✅ | expectException + invoke callable |
toThrow(Exception::class, 'msg') |
✅ | expectException + expectExceptionMessage |
toThrow(new Exception('msg')) |
✅ | expectException + expectExceptionMessage |
toThrow('message') |
✅ | expectExceptionMessage |
not->toThrow() |
✅ | try/catch with $this->fail() on exception |
Laravel-Specific Assertions
| Pest Assertion | Status | PHPUnit Equivalent |
|---|---|---|
toBeCollection |
✅ | assertInstanceOf(Collection::class) |
toBeModel |
✅ | assertInstanceOf(Model::class) |
toBeEloquentCollection |
✅ | assertInstanceOf(EloquentCollection::class) |
Chain Modifiers
| Modifier | Status | Behavior |
|---|---|---|
->not->* |
✅ | Negated equivalents (assertNotSame, assertNotNull, etc.) |
->and($subject) |
✅ | Split into multiple assertion groups |
->each->* (no closure) |
✅ | foreach loop with assertion per item |
->tap(fn() => ...) |
✅ | Closure body inlined |
->pipe(fn($v) => ...) |
✅ | Subject transformed: (fn($v) => ...)($subject) |
Property access (e.g. ->name) |
✅ | $subject->name |
Method access (e.g. ->count()) |
✅ | $subject->count() |
Pest Plugins
| Plugin | Status | Behavior |
|---|---|---|
pest-plugin-faker — fake() |
✅ | Converted to \Faker\Factory::create(), locale arg preserved |
pest-plugin-faker — fake('pt_PT') |
✅ | Converted to \Faker\Factory::create('pt_PT') |
pest-plugin-faker — WithFaker trait |
✅ | Works via uses() trait conversion, $this->faker preserved |
use function Pest\Faker\fake; |
✅ | Import automatically removed |
use function Pest\Laravel\{get, post}; |
✅ | Grouped imports automatically removed |
use function Pest\Livewire\livewire; |
✅ | Import automatically removed |
Silently Stripped (debug/dev-only)
These Pest methods are removed from the chain without emitting any output:
| Modifier | Reason |
|---|---|
->dd() / ->ddWhen() / ->ddUnless() |
Debug — dump and die |
->ray() |
Debug — Ray debugger |
->json() |
Output modifier — no assertion equivalent |
->defer() |
Timing modifier — no assertion equivalent |
Converted to markTestSkipped ⚠️
These features have no PHPUnit equivalent and are converted to skipped tests with a review comment:
| Pest Feature | PHPUnit Output |
|---|---|
arch() tests |
$this->markTestSkipped('Arch test not supported in PHPUnit: ...') |
Higher-order it('...')->expect([...])->toBeUsed() |
$this->markTestSkipped('Arch test not supported in PHPUnit: ...') |
Emits // TODO Comment ⚠️
These features emit a TODO comment because they require manual conversion:
| Pest Feature | TODO Comment |
|---|---|
->sequence(...) |
// TODO(Pest): ->sequence() requires manual conversion to PHPUnit |
->match(...) |
// TODO(Pest): ->match() requires manual conversion to PHPUnit |
->scoped(...) |
// TODO(Pest): ->scoped() requires manual conversion to PHPUnit |
->each(fn() => ...) (with closure) |
// TODO(Pest): ->each(closure) requires manual conversion to PHPUnit |
->when(...) / ->unless(...) |
// TODO(Pest): ->when()/->unless() requires manual conversion to PHPUnit |
Unknown ->toXxx() expectations |
// TODO(Pest): Unknown expectation ->toXxx() has no PHPUnit equivalent |
Laravel / Livewire assert*() Methods ✅
When expect() wraps a Laravel TestResponse, Livewire Testable, or any object with assert*() methods, they are emitted as direct method calls on the subject:
This works automatically for all assert*() methods — no special mapping needed since these methods already throw PHPUnit assertions internally. Verified coverage includes:
Laravel TestResponse:
| Category | Methods |
|---|---|
| Status | assertOk, assertCreated, assertNotFound, assertForbidden, assertUnauthorized, assertUnprocessable, assertStatus, assertSuccessful, assertNoContent |
| Content | assertSee, assertDontSee, assertSeeText, assertSeeInOrder, assertSeeTextInOrder |
| JSON | assertJson, assertExactJson, assertJsonFragment, assertJsonMissing, assertJsonStructure, assertJsonCount, assertJsonPath, assertJsonValidationErrors, assertJsonMissingValidationErrors |
| Redirects | assertRedirect, assertRedirectContains, assertRedirectToRoute, assertLocation |
| Headers | assertHeader, assertHeaderMissing |
| Validation | assertValid, assertInvalid, assertSessionHasErrors |
| Session | assertSessionHas, assertSessionHasAll, assertSessionMissing |
| Views | assertViewIs, assertViewHas, assertViewHasAll, assertViewMissing |
| Cookies | assertCookie, assertCookieMissing, assertCookieExpired |
| Downloads | assertDownload |
Livewire Testable:
| Category | Methods |
|---|---|
| Content | assertSee, assertDontSee, assertSeeHtml, assertDontSeeHtml, assertSeeInOrder |
| Properties | assertSet, assertNotSet, assertCount |
| Events | assertDispatched, assertNotDispatched |
| Validation | assertHasErrors, assertHasNoErrors |
| Navigation | assertRedirect, assertRedirectToRoute, assertNoRedirect |
| Other | assertStatus, assertForbidden, assertUnauthorized, assertViewHas, assertViewIs, assertFileDownloaded |
Non-assert methods in the chain (like followRedirects(), set(), call()) are preserved naturally as chained method calls.
Custom Expectations (expect()->extend()) ✅
Custom expectations defined via expect()->extend('name', fn) are parsed and inlined at call sites:
Supports:
- Delegating bodies —
$this->toBeGreaterThan(0)chains (single or multiple statements) - Mixed bodies — local variables +
expect()calls +$this->valueaccess - Arrow functions —
fn () => $this->toBeInstanceOf(Collection::class) - Parameter substitution — closure params mapped to call-site args, with default values
- Negation —
->not->toCustom()correctly inverts the inlined assertions - Composition — custom expectations calling other custom expectations
->eachmodifier —expect([1,2,3])->each->toBePositive()- Complex bodies — arbitrary code gets best-effort conversion with a
// TODO(Pest)comment
Not Supported
| Pest Feature | Notes |
|---|---|
Higher-order test methods (e.g. it('...')->assertTrue()) |
Not converted |
beforeAll/afterAll inside describe() |
No clean PHPUnit equivalent without multiple classes |
Limitations
- Not a 100% migration tool. Some Pest features have no direct PHPUnit equivalent — these are converted to skipped tests or TODO comments prompting manual review.
- Assertion coverage is broad (60+ mappings including negations). Custom expectations via
expect()->extend()are inlined automatically, though complex bodies may require manual review. - Method naming uses
snake_casewith atest_prefix. Longdescribe()chains can produce long method names. - File structure — the rule generates a class in-place. You may need to manually adjust namespaces or file locations.
- String format assertions (
toBeSnakeCase,toBeUuid, etc.) use regex approximations that may not match Pest's exact validation logic.
Development
Adding test fixtures
Test fixtures live in tests/Rector/Fixture/ as .php.inc files with the format:
If the file should remain unchanged (no Pest code), omit the ----- separator.
License
MIT. See LICENSE.
Credits
Built by Helge Sverre on top of Rector and nikic/php-parser.