sebastianbergmann/phpunit · error · UnknownClassOrInterfaceException

Class or interface "%s" does not exist

Error message

Class or interface "%s" does not exist

What it means

Assert::assertInstanceOf() validates its first argument before evaluating the constraint: if the string is neither an existing nor autoloadable class or interface, it throws PHPUnit\Framework\UnknownClassOrInterfaceException ('Class or interface "%s" does not exist') instead of running the assertion. This is a test-code error (malformed assertion), not an assertion failure — the $actual value is irrelevant because the check fails first.

Source

Thrown at src/Framework/Assert.php:1870

    }

    /**
     * Asserts that a variable is of a given type.
     *
     * @template ExpectedType of object
     *
     * @param class-string<ExpectedType> $expected
     *
     * @throws Exception
     * @throws ExpectationFailedException
     * @throws UnknownClassOrInterfaceException
     *
     * @phpstan-assert =ExpectedType $actual
     */
    final public static function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void
    {
        if (!class_exists($expected) && !interface_exists($expected)) {
            throw new UnknownClassOrInterfaceException($expected);
        }

        self::assertThat(
            $actual,
            new IsInstanceOf($expected),
            $message,
        );
    }

    /**
     * Asserts that a variable is not of a given type.
     *
     * @template ExpectedType of object
     *
     * @param class-string<ExpectedType> $expected
     *
     * @throws Exception
     * @throws ExpectationFailedException

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Replace string literals with the ::class constant and a use import: self::assertInstanceOf(UserRepo::class, $user).
  2. Fix the FQCN spelling/namespace and run class_exists('The\Fqcn') in 'php -r' to confirm the autoloader resolves it.
  3. Run 'composer dump-autoload' (or a full 'composer install') if the class exists on disk but is not autoloaded.
  4. For dynamic class names, validate the string before the assertion with class_exists()/interface_exists() and fail with a clear message.

Example fix

// before
self::assertInstanceOf('App\Servic\UserRepository', $user);

// after
use App\Service\UserRepository;

self::assertInstanceOf(UserRepository::class, $user);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!class_exists($expected) && !interface_exists($expected)) {
    self::fail('Unknown class or interface: ' . $expected . ' (fix the FQCN or autoload mapping)');
}

self::assertInstanceOf($expected, $actual);

Type guard

/** @phpstan-assert class-string $name */
function assertValidTypeString(string $name): void
{
    if (!class_exists($name) && !interface_exists($name)) {
        throw new InvalidArgumentException("Class or interface '{$name}' does not exist");
    }
}

Prevention

When it happens

Trigger: self::assertInstanceOf('App\Servic\UserRepo', $user) with a typo; a class string built from a variable, config, or data provider that is empty, stale, or differently cased; quoting the ::class constant so it stays a literal ('User::class'); referencing a class from a package missing from the current vendor tree.

Common situations: Refactors that rename classes while tests keep old string names; 'composer install --no-dev' CI runs missing dev-only packages; case-sensitive class names on Linux after a rename; leading-backslash inconsistencies in FQCNs built by concatenation.

Related errors


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