sebastianbergmann/phpunit · error · UnknownTypeException

Class or interface "%s" does not exist

Error message

Class or interface "%s" does not exist

What it means

Before building a test double for a type name, PHPUnit's Generator::ensureKnownType() checks that the string denotes an existing class or interface; otherwise it throws UnknownTypeException. This is the first gate inside testDouble() and catches bad type names before reflection or code generation is attempted.

Source

Thrown at src/Framework/MockObject/Generator/Generator.php:697

                '__halt_compiler' => true,
            ];

            if (version_compare(PHP_VERSION, '8.5', '>=')) {
                self::$excludedMethodNames['__sleep']  = true;
                self::$excludedMethodNames['__wakeup'] = true;
            }
        }

        return isset(self::$excludedMethodNames[$name]);
    }

    /**
     * @throws UnknownTypeException
     */
    private function ensureKnownType(string $type): void
    {
        if (!class_exists($type) && !interface_exists($type)) {
            throw new UnknownTypeException($type);
        }
    }

    /**
     * @param ?list<non-empty-string> $methods
     *
     * @throws DuplicateMethodException
     * @throws InvalidMethodNameException
     */
    private function ensureValidMethods(?array $methods): void
    {
        if ($methods === null) {
            return;
        }

        foreach ($methods as $method) {
            if (preg_match('~\A[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z~', (string) $method) === 0) {
                throw new InvalidMethodNameException((string) $method);

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Correct the type name; prefer SomeClass::class over string literals so the autoloader resolves it at compile time
  2. Run composer dump-autoload and make sure the file/package defining the type is installed and autoloadable in the test environment

Example fix

// before
$mock = $this->createMock('App\Service\Mailler'); // typo

// after
use App\Service\Mailer;
$mock = $this->createMock(Mailer::class);
Defensive patterns

Strategy: validation

Validate before calling

if (!class_exists($type) && !interface_exists($type)) {
    $this->markTestSkipped("Cannot double unknown type: $type");
}
$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('App\Servces\Mailer') (typo), createMock($typeName) with a dynamic name that is null/empty/mangled, createMock(SomeClass::class) where SomeClass is not imported or its package is not autoloaded in the test runtime, or names built by string concatenation with wrong separators (leading backslash is fine, wrong case or missing namespace is not).

Common situations: Data-driven tests generating mock types from configuration; refactors renaming classes while the string literal in tests lags behind; composer classmap stale after new files added; running an isolated test file whose dependencies were never autoloaded; CI missing a dev package.

Related errors


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