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 readonly
What it means
Thrown by PHPUnit's TestDoubleBuilder::doubleProperties() when you ask it to generate property hooks (get/set) for a property that is declared readonly. The test double is generated as a subclass of the mocked type, and PHP forbids redeclaring a readonly property with hooks in a child class, so PHPUnit rejects the request up front via ReflectionProperty::isReadOnly().
Source
Thrown at src/Framework/MockObject/TestDoubleBuilder.php:139
}
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;
}
/**
* Specifies the arguments for the constructor.View on GitHub (pinned to f123cdb2a2)
Solutions
- Remove the readonly property from the list passed to doubleProperties()
- If you only need a fixed value, keep the property untouched and configure it through the constructor (setConstructorArgs()) instead of doubling it
- Restructure the test to assert the real value instead of configuring get/set hook expectations for that property
- If the type genuinely needs hook doubling in tests, drop 'readonly' from the property declaration in the production code
Example fix
// before
$double = (new TestDoubleBuilder(Invoice::class))
->doubleProperties(['lines', 'total']) // 'total' is readonly
->generate();
// after
$double = (new TestDoubleBuilder(Invoice::class))
->doubleProperties(['lines']) // only double non-readonly properties
->setConstructorArgs([['line 1'], 100])
->generate(); Defensive patterns
Strategy: type-guard
Validate before calling
$reflector = new ReflectionClass(Invoice::class);
$doublable = array_filter(
['lines', 'total'],
static fn (string $name) => $reflector->hasProperty($name)
&& $reflector->getProperty($name)->isPublic()
&& !$reflector->getProperty($name)->isStatic()
&& !$reflector->getProperty($name)->isReadOnly(),
); Type guard
/** @param class-string $class @param list<string> $properties @return list<string> */
function doublableProperties(string $class, array $properties): array
{
$reflector = new ReflectionClass($class);
return array_values(array_filter(
$properties,
static fn (string $name): bool => $reflector->hasProperty($name)
&& $reflector->getProperty($name)->isPublic()
&& !$reflector->getProperty($name)->isStatic()
&& !$reflector->getProperty($name)->isReadOnly(),
));
} Prevention
- Run doublableProperties() over the list before calling doubleProperties() in shared test helpers
- Prefer configuring readonly properties through setConstructorArgs() with real values
- Review the class's property modifiers before adding new names to a doubling list
When it happens
Trigger: Calling doubleProperties(['someProperty']) (or a builder/wrapper that forwards to it) where someProperty is declared 'readonly' or is a readonly promoted constructor property of the doubled class.
Common situations: Trying to double properties on PHP 8.1+ value objects / DTOs that use readonly promoted constructor properties; upgrading a codebase to readonly and re-running an older test suite that doubled those properties.
Related errors
- Trying to double property "%s" of class "%s" with doubleProp
- Return value for %s::%s() cannot be generated: %s
- Trying to double property "%s" of class "%s" with doubleProp
- Comparison method %s::%s() does not exist.
- Cannot double method with invalid name "%s"
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/29a926addaf452b7.
Report an issue: GitHub.