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 does not exist
What it means
doubleProperties() requires every passed name to be an actually declared property of the target class — ReflectionClass::hasProperty() must find it. If the name is unknown (typo, undeclared dynamic property, wrong class), PHPUnit throws PropertyCannotBeDoubledException with the reason 'it does not exist' (TestDoubleBuilder.php:123-126). Unlike addMethods() for methods, there is no API for adding properties to a test double, so the name must be real.
Source
Thrown at src/Framework/MockObject/TestDoubleBuilder.php:125
*/
public function doubleProperties(array $properties): static
{
try {
$reflector = new ReflectionClass($this->type);
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$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');View on GitHub (pinned to f123cdb2a2)
Solutions
- Verify against the class: ReflectionProperty check or var_dump of get_class_vars(User::class) to get the exact declared name, then correct the string in doubleProperties().
- If the property was renamed upstream, update the test to the new name.
- Remove the name from the list if the property does not need hook doubling — undeclared/dynamic properties cannot be doubled at all.
- If the property is declared on a parent/trait, mock the class that actually declares it, or double it there and extend the mock.
Example fix
// before
$mock = $this->getMockBuilder(User::class)
->doubleProperties(['emial']) // typo: declared property is $email
->getMock();
// after
$mock = $this->getMockBuilder(User::class)
->doubleProperties(['email'])
->getMock(); Defensive patterns
Strategy: validation
Validate before calling
// Verify declared properties before doubling:
$reflector = new ReflectionClass(User::class);
foreach (['email', 'name'] as $prop) {
if (!$reflector->hasProperty($prop)) {
self::fail("User::\${$prop} is not a declared property (docblock @property cannot be doubled)");
}
}
$mock = $this->getMockBuilder(User::class)->doubleProperties(['email', 'name'])->getMock(); Type guard
/** @param list<non-empty-string> $props @return list<non-empty-string> */
function declaredProperties(string $class, array $props): array
{
$r = new ReflectionClass($class);
return array_values(array_filter($props, static fn (string $p): bool => $r->hasProperty($p)));
} Try / catch
use PHPUnit\Framework\MockObject\PropertyCannotBeDoubledException;
try {
$builder->doubleProperties($names);
} catch (PropertyCannotBeDoubledException $e) {
self::fail('Property cannot be doubled: ' . $e->getMessage());
} Prevention
- Take property names from the class source (or Reflection), not from docblock @property annotations.
- Update doubleProperties() lists in the same commit that renames entity fields.
- Remember there is no addProperties() counterpart — undeclared/dynamic properties cannot be doubled at all.
- When a property lives on a parent class, double it against the declaring class.
When it happens
Trigger: $this->getMockBuilder(User::class)->doubleProperties(['emial']) where User declares $email but not $emial: hasProperty('emial') returns false and PropertyCannotBeDoubledException is thrown with 'it does not exist'. Also when the property exists only as a docblock @property annotation, is a magic __get/__set shadow, or lives on a different class than the one being mocked.
Common situations: Typos or renames of entity properties after a migration/refactor; assuming a parent-class or trait property exists on the mocked subclass when it actually does not; trying to double runtime-assigned dynamic properties (not declared in the class); @property-read annotations on DTOs mistaken for real declarations; copying doubleProperties() lists between related classes.
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/7a88395254c5a0f2.
Report an issue: GitHub.