crodas/phpunit-compat
A PHPUnit compatibility package that helps bridge version differences, making test suites easier to run across varied PHPUnit environments with fewer compatibility issues.
This package is a stop-gap shim that lets a PHPUnit 5.x-era test suite run on PHPUnit 6.x, where the framework moved into the PHPUnit\Framework\ namespace and dropped a number of legacy helpers. It works by registering class aliases (and usually a compatibility TestCase) so old code like extends PHPUnit_Framework_TestCase and $this->setExpectedException(...) keeps resolving.
Minimal setup:
composer require --dev crodas/phpunit-compat
composer require --dev phpunit/phpunit:^6.0
composer.json — look for an autoload.files entry (the alias bootstrap) and any conflict/require constraints on phpunit/phpunit.src/ — find the compat TestCase (something like Crodas\PhpUnit\Compat\TestCase) and the trait/class that re-adds missing methods (assertInternalType, getMock, setExpectedException, assertAttribute*).README — confirm whether you're expected to change your test base class or rely purely on the global aliases.<phpunit bootstrap="vendor/autoload.php">
composer dump-autoload
php -r "require 'vendor/autoload.php'; var_dump(class_exists('PHPUnit_Framework_TestCase'));" # true
vendor/bin/phpunit
First real use case: take an existing 5.x suite that currently fails with Class 'PHPUnit_Framework_TestCase' not found under PHPUnit 6, add the package, and get the suite green again without touching dozens of test files. From there, migrate incrementally rather than treating the shim as permanent.
Alias-only mode (least intrusive). If the package registers class_alias shims through autoload.files, leave every test file untouched. PHPUnit_Framework_TestCase, PHPUnit_Framework_Assert, and the common PHPUnit_Framework_Constraint_* names resolve to their namespaced equivalents at runtime. This is the fastest way to unblock CI on a large, otherwise-healthy suite.
Project-owned base class (recommended). Create a single compat base in your own tree so you control upgrades and can add project helpers:
// tests/CompatTestCase.php
namespace Tests;
use Crodas\PhpUnit\Compat\TestCase as CompatBase;
abstract class CompatTestCase extends CompatBase
{
// project-wide helpers, e.g. fixtures, DB traits
}
Then either keep extends PHPUnit_Framework_TestCase (aliased) or sed your suite to extends \Tests\CompatTestCase. The latter is one mechanical change and gives you a seam to swap in PHPUnit\Framework\TestCase later.
Bridge old and new API in the same suite. The shim exists to buy time, so write new tests against the PHPUnit 6 API while old ones keep working:
| PHPUnit 5.x (shimmed) | PHPUnit 6.x (preferred for new tests) |
|---|---|
setExpectedException(Foo::class) |
$this->expectException(Foo::class) |
getMock(Foo::class) |
$this->createMock(Foo::class) |
getMockForAbstractClass(...) |
$this->getMockBuilder(...)->getMockForAbstractClass() |
assertInternalType('array', $x) |
assertIsArray($x) |
@expectedException docblock |
expectException() in the test body |
Mocking translation. Old-style getMock($class, ['method'], [$ctorArg]) should be verified per call site — the shim typically forwards to getMockBuilder, and the positional $methods / $arguments / $mockClassName / $callOriginalConstructor arguments do not always map one-to-one. When a mock assertion starts failing mysteriously, rewrite that individual call to the builder rather than debugging the shim.
Incremental migration with a ratchet. Add a CI step that greps for legacy symbols and asserts the count only goes down:
! grep -rlE 'setExpectedException|assertInternalType|->getMock\(' tests/ | head -n1
Combine with Rector's phpunit60 ruleset to automate the bulk rename, using the shim to keep the suite runnable between batches.
Pin the platform. Because the package targets exactly the 5→6 boundary, lock it down:
{
"require-dev": {
"phpunit/phpunit": "^6.0",
"crodas/phpunit-compat": "*"
},
"conflict": { "phpunit/phpunit": "<6.0 || >=7.0" }
}
This prevents a future composer update from pulling PHPUnit 7+, where the shim's assumptions (and some method signatures) no longer hold.
require vendor/autoload.php, you'll get Class 'PHPUnit_Framework_TestCase' not found. Composer's autoload.files runs at autoload time, so any entry point that skips Composer skips the shim. Also re-run composer dump-autoload after install — cached autoloaders won't pick up new files entries.class_alias cannot add methods. Aliasing only renames classes. Removed methods like setExpectedException, assertInternalType, getMock, and the assertAttribute* family only work if the package supplies a compat TestCase/trait that actually re-implements them. Confirm which of the two mechanisms the package uses before assuming a legacy call will work.@expectedException docblocks are gone. No shim can restore annotation-driven exception expectations because the annotation parser itself was removed. These tests will silently stop asserting the exception — they'll pass without ever checking. Grep for @expectedException and convert them by hand; a passing test here is worse than a failing one.setExpectedException accumulates state. When it's shimmed onto expectException*, expectations set in a test apply to that test only, but if you call it in setUp() for a shared failure mode, be aware the compat layer may not reset between tests consistently. Prefer per-test calls.assertInternalType type strings differ. The old string vocabulary ('numeric', 'scalar', 'real') has no direct PHPUnit 6 equivalent. Expect 'numeric' and 'real' to break; replace with assertIsNumeric/assertIsFloat or a greaterThanOrEqual comparison.getMock accepted $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload positionally. Verify each call site that passed more than one argument; the shim will often accept them and produce a subtly wrong mock.--process-isolation is fine, but PHP subprocesses need Composer too. Aliases are per-process; isolated runs re-bootstrap, so the alias file must be reachable from whatever bootstrap they use. It is — as long as bootstrap stays vendor/autoload.php.Error: Declaration ... must be compatible. This package is a 6.x-only bridge.vendor/, add a Tests\LegacyCompatTrait with any project-specific legacy helpers the package misses, and use it in your base class. That keeps the workaround version-controlled with your code and easy to delete when the migration is done.var_dump(class_exists('PHPUnit_Framework_TestCase')) in setUp-adjacent bootstrap code; ReflectionClass the resolved class to see whether it's the real namespaced class or a compat subclass; then check whether the failing method exists on that class at all. Most issues are "alias worked, method never existed."How can I help you explore Laravel packages today?