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

  1. Use a real enum case in the test instead of a mock (enums are cheap immutable values)
  2. If the enum implements an interface, mock the interface: createMock(FooInterface::class), since the SUT should type-hint the interface
  3. 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

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


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/9207a0c09deaf1d5. Report an issue: GitHub.