sebastianbergmann/phpunit · error · PropertyCannotBeDoubledException
Trying to double property "%s" of class "%s" with doubleProp
Error message
Trying to double property "%s" of class "%s" with doubleProperties(), but it is static
What it means
doubleProperties() generates per-instance get/set hooks for doubled properties, but static properties are per-class state shared across all instances, so instance hooks are meaningless for them. After existence and public checks pass, ReflectionProperty::isStatic() returning true triggers PropertyCannotBeDoubledException with reason 'it is static' (TestDoubleBuilder.php:134-136). Only non-static public instance properties can be doubled.
Source
Thrown at src/Framework/MockObject/TestDoubleBuilder.php:135
$e->getCode(),
$e,
);
// @codeCoverageIgnoreEnd
}
foreach ($properties as $propertyName) {
if (!$reflector->hasProperty($propertyName)) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not exist');
}
$property = $reflector->getProperty($propertyName);
if (!$property->isPublic()) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is not public');
}
if ($property->isStatic()) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is static');
}
if ($property->isReadOnly()) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is readonly');
}
if ($property->isFinal()) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it is final');
}
if (!$property->hasType()) {
throw new PropertyCannotBeDoubledException($this->type, $propertyName, 'it does not declare a type');
}
}
$this->doubledProperties = array_merge($this->doubledProperties, $properties);
return $this;View on GitHub (pinned to f123cdb2a2)
Solutions
- Do not double the static property; fake static state through the class's static methods (mock a non-static collaborator instead, or redesign to instance state / dependency injection).
- Reset static state in setUp()/tearDown() directly (Registry::$instances = []) rather than mocking it.
- If the design allows, convert the property to a public non-static instance property so doubling applies.
- Remove the name from doubleProperties() — static properties are categorically unsupported and the test must change approach.
Example fix
// before
class Registry { public static array $instances = []; }
$mock = $this->getMockBuilder(Registry::class)
->doubleProperties(['instances']) // 'it is static'
->getMock();
// after: reset the static state, no property doubling
protected function tearDown(): void
{
Registry::$instances = [];
} Defensive patterns
Strategy: validation
Validate before calling
$reflector = new ReflectionClass(Registry::class);
foreach ($props as $name) {
if ($reflector->hasProperty($name) && $reflector->getProperty($name)->isStatic()) {
self::fail("\${$name} is static — reset it in setUp/tearDown instead of doubling");
}
} Type guard
/** @param list<non-empty-string> $props @return list<non-empty-string> */
function instancePropertiesOnly(string $class, array $props): array
{
$r = new ReflectionClass($class);
return array_values(array_filter(
$props,
static fn (string $p): bool => $r->hasProperty($p) && !$r->getProperty($p)->isStatic(),
));
} Try / catch
use PHPUnit\Framework\MockObject\PropertyCannotBeDoubledException;
try {
$builder->doubleProperties($names);
} catch (PropertyCannotBeDoubledException $e) {
if (str_contains($e->getMessage(), 'it is static')) {
// handle static state directly, no doubling
Registry::$instances = [];
$builder = $this->getMockBuilder(Registry::class); // rebuild without the static prop
} else {
throw $e;
}
} Prevention
- Never list static properties in doubleProperties(); they cannot be doubled by design.
- Reset static state in setUp()/tearDown() explicitly when tests touch classes with global state.
- Prefer injecting stateful collaborators over static registries so mocks can replace them.
- When refactoring a property to static, search tests for doubleProperties() usages in the same change.
When it happens
Trigger: $this->getMockBuilder(Registry::class)->doubleProperties(['instances']) where Registry declares public static array $instances: the property exists and is public, but isStatic() is true, so the 'it is static' exception is thrown from the validation loop and the builder stops before generating the double.
Common situations: Registry/configuration/counter classes that keep global state in public static fields; refactor introducing a static cache property on a class whose other properties were being doubled; test authors reaching for property doubling to reset or fake global static state; static properties under inheritance (declared on a parent) behaving differently from the subclass the test mocks.
Related errors
- Trying to double property "%s" of class "%s" with doubleProp
- Trying to double property "%s" of class "%s" with doubleProp
- Trying to configure method "%s" with onlyMethods(), but it d
- Comparison method %s::%s() does not exist.
- Return value for %s::%s() cannot be generated: %s
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/78813403633551a7.
Report an issue: GitHub.