Skip to content

Instantly share code, notes, and snippets.

Created March 31, 2022 17:16
Show Gist options
  • Save Ocramius/d5f797cd30f64b2214cf620aff5ea3d7 to your computer and use it in GitHub Desktop.
Save Ocramius/d5f797cd30f64b2214cf620aff5ea3d7 to your computer and use it in GitHub Desktop.
namespace Tests\Integration;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionProperty;
use Webmozart\Assert\Assert;
use function array_filter;
use function array_key_exists;
use function array_map;
use function array_merge;
use function array_walk;
use function gc_collect_cycles;
use function get_class;
use function strpos;
final class ClearAllNonPhpunitProperties
/** @psalm-var array<class-string, array<ReflectionProperty>> */
private static array $propertiesToBeUnsetCache = [];
* Removes all state of the given {@see TestCase} by doing an `unset()` on all properties that
* were not declared by the parent {@see TestCase} class.
* PHPUnit "leaks by design", because it keeps {@see TestCase} instances in memory until the full suite
* is through, at which time it traverses all {@see TestCase} instances again to generate a test report.
* PHPUnit's design is simplistic and works, but leads to memory leaks, and sometimes clutters
* connections to external services which would otherwise be terminated (thanks to garbage collection).
public static function unsetNonPHPUnitObjectProperties(TestCase $instance): void
$className = get_class($instance);
if (array_key_exists($className, self::$propertiesToBeUnsetCache)) {
static function (ReflectionProperty $property) use ($instance): void {
$unset = (static function (TestCase $instance) use ($property): void {
->bindTo(null, $property->getDeclaringClass()->getName());
self::$propertiesToBeUnsetCache[$className] = array_filter(
self::getAllPropertiesForClasses(self::getClasses(new ReflectionClass($className))),
[self::class, 'isNotPhpunitInternalProperty']
// Recursion - cache is not empty, so this will short-circuit
/** @return non-empty-list<ReflectionClass<object>> */
private static function getClasses(ReflectionClass $thisClass): array
$parentClass = $thisClass->getParentClass();
if ($parentClass instanceof ReflectionClass) {
return array_merge([$thisClass], self::getClasses($parentClass));
return [$thisClass];
private static function isNotPhpunitInternalProperty(ReflectionProperty $property): bool
return strpos($property->getDeclaringClass()->getName(), 'PHPUnit\\') !== 0;
* @param non-empty-list<ReflectionClass> $classes
* @return ReflectionProperty[]
private static function getAllPropertiesForClasses(array $classes): array
return array_filter(
...array_map(static function (ReflectionClass $class): array {
return $class->getProperties();
}, $classes)
static fn (ReflectionProperty $property): bool => ! $property->isStatic()
Copy link

Ocramius commented Apr 6, 2022

Considering that prophecy is gone-gone-gone in all my projects, IMO not to be done :P

I'd totally add it on an upstream patch to phpunit itself though 👍

Copy link

Seldaek commented Apr 6, 2022

Yeah we still have some leftover Prophecy bits here so I ran into problems, just sharing if it helps anyone else passing by here :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment