Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

testo/bridge-rector

Rector rules to convert test suites between Pest, PHPUnit and Testo.

Directions & config sets

Each direction ships a Rector set. Reference it with the typed handle from Testo\Bridge\Rector\Set\TestoRectorSetList rather than the raw file path:

Direction Set constant Status
Testo → PHPUnit TestoRectorSetList::TESTO_TO_PHPUNIT Assert calls, Expect::exception (bare), throw SkipTest, #[Covers]→#[CoversClass], lifecycle attributes.
PHPUnit → Testo TestoRectorSetList::PHPUNIT_TO_TESTO Assert calls (arg-order restored), expectException (bare), markTestSkipped, #[CoversClass]→#[Covers], lifecycle methods → attributes.
Pest → Testo TestoRectorSetList::PEST_TO_TESTO expect()->toX() → Assert::* only. The functional→class restructuring (test()/it() → methods) is not automatable — see src/PestToTesto/TODO.md.

The set files live under config/; the constants are absolute paths to those files, so they also work with $rectorConfig->import(...).

Mock sets

Testo core ships no mocking, so test doubles convert through their own sets, one per target library. Add one next to PHPUNIT_TO_TESTO, or run it on its own:

Direction Set constant Target
PHPUnit → Double TestoRectorSetList::PHPUNIT_TO_DOUBLE createMock/createStub and their expects/method/will*/with chains → \JMac\Testing\Double (testo/bridge-double). See src/PhpunitToDouble/TODO.md.
PHPUnit → Mockery TestoRectorSetList::PHPUNIT_TO_MOCKERY The same chains → \Mockery::mock() + shouldReceive() (testo/bridge-mockery). See src/PhpunitToMockery/TODO.md.
Mockery → Double TestoRectorSetList::MOCKERY_TO_DOUBLE mock/spy, shouldReceive/allows/expects, shouldHaveReceived, Mockery::close() → Double, after Double's migration table. See src/MockeryToDouble/TODO.md.
return RectorConfig::configure()
    ->withPaths([__DIR__ . '/tests'])
    ->withSets([TestoRectorSetList::PHPUNIT_TO_TESTO, TestoRectorSetList::PHPUNIT_TO_DOUBLE]);

Each mock rule rewrites a whole configuration statement or none of it. The rules do not track a double across statements, though: when one statement on a double is left for manual work while the double's factory and its other statements convert, the leftover one is what the finishing pass fixes.

Conversions that have no faithful counterpart in the target framework (constraints, memory-leak / retry / repeat, Pest higher-order & arch() tests, etc.) are not silently dropped: each is a documented stub rule plus an entry in the direction's TODO.md.

Argument order (important)

Testo's comparison assertions are (actual, expected); PHPUnit's are (expected, actual). Both AssertCall* rules swap the first two arguments accordingly — getting this wrong would silently invert every comparison, so it is covered by fixtures.

Why Testo → PHPUnit

Testo is self-hosted: the engine that discovers and runs tests is the same code Infection mutates. A mutation on the run path (e.g. Sorter, PipeOptions) can break discovery itself, producing spurious survivors/kills instead of a real mutation signal. Converting the unit-style self-tests to PHPUnit lets Infection's PHPUnit adapter run them on a runner that shares no code with the mutated engine — a mutation can then only be caught (or missed) by an assertion, never by breaking the harness.

Usage

// rector.php
use Rector\Config\RectorConfig;
use Testo\Bridge\Rector\Set\TestoRectorSetList;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/tests'])
    ->withSets([TestoRectorSetList::TESTO_TO_PHPUNIT]);

Testing the rules — "inline tests for rules"

The rules are tested by Testo itself, with no PHPUnit dependency. A rule carries #[\Testo\Bridge\Rector\Testing\TestRectorFixtures('<dir>')] pointing at co-located *.php.inc fixtures (input + expected, separated by a ----- line; no separator = "must stay unchanged"). Each declared path is relative to the rule's own directory (or absolute) and must resolve within the working directory — an escaping path is rejected; declaring the attribute with no paths tests nothing. The reusable harness lives in src/Testing/ — attach RectorTestingPlugin to a suite whose finder scans the rule sources, and each fixture is run through a freshly-booted Rector container and reported as its own data set. Fixtures are export-ignored; the harness ships so downstream rule authors can reuse it (testo/* are require-dev + suggest).

Coverage

Rector runs in the test process, so the fixtures count toward code coverage like any test. Each fixture's coverage is scoped to the rule it exercises (the harness attaches a Testo\Codecov\CoverageScope), which keeps the harness itself and the rest of the run out of it. A rule that delegates to helpers of its own widens the scope with #[Covers] on the rule class — list the rule too, since a declared #[Covers] replaces the default:

#[TestRectorFixtures('MyRule')]
#[Covers(MyRule::class)]
#[Covers(MyHelper::class)]
final class MyRule extends AbstractRector { /* ... */ }

#[CoversNothing] on the rule class keeps its fixtures out of coverage.

About

Rector rules to convert test suites between Pest, PHPUnit and Testo (split-published from php-testo/testo)

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages