sebastianbergmann/phpunit · error · ClassIsEnumerationException
Class "%s" is an enumeration and cannot be doubled
Error message
Class "%s" is an enumeration and cannot be doubled
What it means
PHPUnit cannot create test doubles for enums: ReflectionClass::isEnum() on the target type triggers this exception. Enums are final-like constructs with singleton cases; the generated-subclass strategy of the mock generator cannot apply to them.
Source
Thrown at src/Framework/MockObject/Generator/Generator.php:390
$type,
$mockClassName,
$testDoubleClassPrefix,
);
if (class_exists($_mockClassName['fullClassName'])) {
$isClass = true;
} elseif (interface_exists($_mockClassName['fullClassName'])) {
$isInterface = true;
}
$class = $this->reflectClass($_mockClassName['fullClassName']);
if ($class->isAnonymous()) {
throw new ClassIsAnonymousException($_mockClassName['fullClassName']);
}
if ($class->isEnum()) {
throw new ClassIsEnumerationException($_mockClassName['fullClassName']);
}
if ($class->isFinal()) {
throw new ClassIsFinalException($_mockClassName['fullClassName']);
}
if ($class->isReadOnly()) {
$isReadonly = true;
}
// @see https://github.com/sebastianbergmann/phpunit/issues/2995
if ($isInterface && $class->implementsInterface(Throwable::class)) {
$actualClassName = Exception::class;
$additionalInterfaces[] = $class->getName();
$isInterface = false;
$class = $this->reflectClass($actualClassName);
foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) {View on GitHub (pinned to f123cdb2a2)
Solutions
- Use a real enum case in the test instead of a mock (enums are cheap immutable values)
- If the enum implements an interface, mock the interface: createMock(FooInterface::class), since the SUT should type-hint the interface
- Refactor logic that 'needs' a mocked enum behind an injectable collaborator and mock that
Example fix
// before $status = $this->createMock(OrderStatus::class); // enum -> exception // after $status = OrderStatus::Paid; // or, when the enum implements an interface: $status = $this->createMock(HasLabel::class);
Defensive patterns
Strategy: validation
Validate before calling
if ((new ReflectionClass($type))->isEnum()) {
// Enums are plain values: use a real case instead of mocking
$value = constant($type . '::' . $caseName);
}
$mock = $this->createMock($type); Type guard
function isMockableType(string $type): bool
{
if (!class_exists($type) && !interface_exists($type)) {
return false;
}
$r = new ReflectionClass($type);
return !$r->isAnonymous() && !$r->isEnum() && !$r->isFinal();
} Prevention
- Use real enum cases in tests — they are immutable values, not collaborators
- When enum behavior must be faked, mock the interface the enum implements and type-hint that interface in the SUT
When it happens
Trigger: createMock(SomeEnum::class) or createStub(SomeEnum::class) where SomeEnum is a PHP 8.1 enum (pure enum or backed enum); also passing an enum name string through MockBuilder::getMockBuilder().
Common situations: Codebases adopting enums for status/type fields and tests still trying to mock them like old constants/class combinations; attempting to stub an enum that carries an interface (enum implements FooInterface) hoping to fake behavior; data providers passing enum names.
Related errors
- Class "%s" is an anonymous class and cannot be doubled
- Class "%s" is declared "final" and cannot be doubled
- Class or interface "%s" does not exist
- At least two interfaces must be specified
- Interface "%s" does not exist
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/9207a0c09deaf1d5.
Report an issue: GitHub.