sebastianbergmann/phpunit · error · InvalidClassNameException

Cannot use "%s" as the name of a test double class because i

Error message

Cannot use "%s" as the name of a test double class because it is not a valid PHP class name

What it means

Thrown by Generator::ensureValidNameForTestDoubleClass() when the requested class name for the generated test double is not a valid PHP class name (same identifier regex as method names; empty string is allowed and skips validation). This name comes from the mockClassName argument, e.g. MockBuilder::setMockClassName(). Note the regex has no backslash, so fully-qualified namespaced names are rejected too.

Source

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

            }
        }

        if ($methods !== array_unique($methods)) {
            throw new DuplicateMethodException($methods);
        }
    }

    /**
     * @throws InvalidClassNameException
     */
    private function ensureValidNameForTestDoubleClass(string $className): void
    {
        if ($className === '') {
            return;
        }

        if (preg_match('~\A[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z~', $className) === 0) {
            throw new InvalidClassNameException($className);
        }
    }

    /**
     * @throws NameAlreadyInUseException
     * @throws ReflectionException
     */
    private function ensureNameForTestDoubleClassIsAvailable(string $className): void
    {
        if ($className === '') {
            return;
        }

        if (class_exists($className, false) ||
            interface_exists($className, false) ||
            trait_exists($className, false)) {
            throw new NameAlreadyInUseException($className);
        }

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Use a bare identifier: letters, digits, underscore, starting with a letter or underscore (setMockClassName('FooMock')).
  2. Do not include a namespace in the mock class name; PHPUnit generates the class in the global mock namespace.
  3. If you do not need a specific name, drop setMockClassName() entirely and let PHPUnit pick a unique name.
  4. Sanitize dynamic names: preg_replace('/[^A-Za-z0-9_]/', '', $name) and prefix with a letter.

Example fix

// before
$mock = $this->getMockBuilder(UserRepo::class)
    ->setMockClassName('App\\Tests\\User-Repo-Mock')
    ->getMock();

// after
$mock = $this->getMockBuilder(UserRepo::class)
    ->setMockClassName('UserRepoMock')
    ->getMock();
Defensive patterns

Strategy: validation

Validate before calling

$mockClassName = 'RepoMock';
if (!preg_match('~\A[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z~', $mockClassName)) {
    $mockClassName = preg_replace('/[^A-Za-z0-9_]/', '', $mockClassName);
}

Type guard

function isPhpClassName(string $name): bool
{
    return (bool) preg_match('~\A[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z~', $name);
}

Try / catch

try {
    $mock = $builder->setMockClassName($name)->getMock();
} catch (PHPUnit\Framework\MockObject\InvalidClassNameException $e) {
    // fall back to an auto-generated name: omit setMockClassName()
}

Prevention

When it happens

Trigger: $this->getMockBuilder(C::class)->setMockClassName('My-Mock') with hyphens, digits first ('Mock123' is fine, '123Mock' is not), spaces, or a namespaced string 'App\\Mocks\\Foo'; calling Generator::testDouble(..., mockClassName: 'not valid') directly.

Common situations: Passing an existing FQCN as the new mock's class name (expecting namespacing support); building mock class names from test data or slugs that contain dashes; copying a class basename with stray characters; migrating tests that used dynamic class names in old PHPUnit versions.

Related errors


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