sanmai/phpunit-legacy-adapter
Compatibility adapter for running legacy PHPUnit test suites on newer PHPUnit versions. Helps bridge API changes, keep older tests passing, and smooth migrations without rewriting everything. Suitable for maintaining long-lived PHP projects with outdated test setups.
Install the Package:
composer require --dev sanmai/phpunit-legacy-adapter:"^6.4 || ^8.2.1"
^6.4 for PHPUnit 4/5/6 (PHP 5.3–7.4).^8.2.1 for PHPUnit 7/8/9/10/11/12/13 (PHP 7.1–8.1).Update Test Classes: Replace the base class in your test files:
- class MyTest extends \PHPUnit\Framework\TestCase
+ class MyTest extends \LegacyPHPUnit\TestCase
Replace Template Methods:
Update method signatures to use the do* variants:
- protected function setUp(): void
+ protected function doSetUp()
Run Tests:
Execute PHPUnit as usual. The adapter handles the rest, bypassing void return type requirements.
Scenario: You have a legacy Laravel project running PHP 7.0 with PHPUnit 8.x, but tests fail due to void return type declarations in setUp()/tearDown().
Solution:
phpunit.xml to use PHPUnit 8.x.setUp(): void with doSetUp() in all test classes.Adapter Initialization:
The package hooks into PHPUnit’s test lifecycle via the \LegacyPHPUnit\TestCase base class. No additional configuration is needed beyond extending this class.
Method Replacement:
The adapter dynamically intercepts calls to legacy template methods (e.g., setUp()) and delegates them to doSetUp(). This avoids PHP 7.0’s inability to declare void return types.
Assertion Polyfills:
While the adapter doesn’t handle assertion changes, you can extend \LegacyPHPUnit\TestCase to add custom __call() logic for missing assertions (e.g., assertStringContainsString):
class MyTest extends \LegacyPHPUnit\TestCase
{
public function __call($method, $args)
{
if ($method === 'assertStringContainsString') {
return $this->assertContains($args[0], $args[1]);
}
throw new \BadMethodCallException("Method {$method} not found.");
}
}
Hybrid Test Suites: Use the adapter selectively in legacy test files while keeping modern tests as-is. Example:
// LegacyTest.php
class LegacyTest extends \LegacyPHPUnit\TestCase { ... }
// ModernTest.php
class ModernTest extends \PHPUnit\Framework\TestCase { ... }
Laravel-Specific:
tests/TestCase.php to avoid per-file updates:
use LegacyPHPUnit\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase { ... }
TestCase in all legacy test files.CI/CD Pipelines:
phpunit/phpunit:^8.2).Static Analysis:
# phpstan.neon
excludes:
- vendor/sanmai/phpunit-legacy-adapter/
Parallel Testing:
php-parallel-lint), but verify with your specific setup.PHPUnit Version Mismatch:
composer.json:
"require-dev": {
"phpunit/phpunit": "^8.2.1"
}
Custom Test Classes:
__call(), __callStatic(), or other magic methods, the adapter may interfere.Static Analysis Warnings:
doSetUp()/doTearDown().Assertion Gaps:
assertSame() behavior differs across PHPUnit versions).yoast/phpunit-polyfills) alongside the adapter.Performance Overhead:
phpunit --coverage-text --colors=never | grep "Time"
Verify Adapter Activation: Add a debug method to confirm the adapter is loaded:
class MyTest extends \LegacyPHPUnit\TestCase
{
public function testAdapterLoaded()
{
$this->assertTrue(method_exists($this, 'doSetUp'));
}
}
Check Method Calls:
Use Xdebug or strace to trace method calls if tests behave unexpectedly:
strace -f -e trace=execve,open phpunit MyTest
Isolate Issues: Test a single legacy file first to rule out conflicts with other packages.
Custom Polyfills:
Extend \LegacyPHPUnit\TestCase to add missing assertions:
class CustomTestCase extends \LegacyPHPUnit\TestCase
{
public function assertIsBool($value)
{
$this->assertTrue(is_bool($value));
}
}
Global Assertion Replacement: Use a trait to centralize polyfills:
trait LegacyAssertions
{
public function assertStringContainsString($needle, $haystack)
{
$this->assertContains($needle, $haystack);
}
}
class MyTest extends \LegacyPHPUnit\TestCase
{
use LegacyAssertions;
}
Bootstrap Integration:
Load the adapter globally via phpunit.xml:
<php>
<autoload>
<classmap prefix="LegacyPHPUnit"/>
</autoload>
</php>
Service Provider Conflicts:
If using Laravel’s PHPUnitServiceProvider, ensure it doesn’t override the test case base class. Override the provider’s register() method:
public function register()
{
$this->app->singleton('testing', function () {
return new TestCase();
});
}
Artisan Test Runner:
The adapter works with php artisan test, but ensure your phpunit.xml points to the correct PHPUnit version:
<phpunit bootstrap="vendor/autoload.php">
<php>
<ini name="memory_limit" value="1024M"/>
</php>
</phpunit>
Database Transactions:
Legacy tests using DatabaseTransactions may interact poorly with the adapter’s lifecycle. Test thoroughly with:
use Illuminate\Foundation\Testing\DatabaseTransactions;
class MyTest extends \LegacyPHPUnit\TestCase
{
use DatabaseTransactions;
}
How can I help you explore Laravel packages today?