sebastianbergmann/phpunit · error · ClassDoesNotExtendTestCaseException

Class %s declared in %s does not extend PHPUnit\Framework\Te

Error message

Class %s declared in %s does not extend PHPUnit\Framework\TestCase

What it means

TestSuiteLoader::load() derives the expected class name from the filename (basename minus .php, cut at the first dot) and, in a fallback loop, looks at all classes loaded from the file: when a loaded class name ends with that expected name (case-insensitive) but no matching class extended PHPUnit\Framework\TestCase (the first loop already skipped non-TestCase and anonymous classes), it throws ClassDoesNotExtendTestCaseException('Class %s declared in %s does not extend PHPUnit\Framework\TestCase').

Source

Thrown at src/Runner/TestSuiteLoader.php:91

            if (!str_ends_with(strtolower($class->getShortName()), strtolower($suiteClassName))) {
                continue;
            }

            if (!$class->isAbstract()) {
                return $class;
            }

            $e = new ClassIsAbstractException($class->getName(), $suiteClassFile);
        }

        if (isset($e)) {
            throw $e;
        }

        foreach ($loadedClasses as $className) {
            if (str_ends_with(strtolower($className), strtolower($suiteClassName))) {
                throw new ClassDoesNotExtendTestCaseException($className, $suiteClassFile);
            }
        }

        throw new ClassCannotBeFoundException($suiteClassName, $suiteClassFile);
    }

    private function classNameFromFileName(string $suiteClassFile): string
    {
        $className = basename($suiteClassFile, '.php');
        $dotPos    = strpos($className, '.');

        if ($dotPos !== false) {
            $className = substr($className, 0, $dotPos);
        }

        return $className;
    }

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Make the class extend PHPUnit\Framework\TestCase: class ExampleTest extends \PHPUnit\Framework\TestCase.
  2. If the class is a helper, move it out of the file you pass to phpunit or rename the file so phpunit does not treat it as the test class.
  3. Check intermediate base classes: the whole chain must bottom out at PHPUnit\Framework\TestCase (isSubclassOf is used, so traits do not count).

Example fix

// before
class ExampleTest
{
    public function testItWorks(): void
    {
        assert(true);
    }
}

// after
class ExampleTest extends \PHPUnit\Framework\TestCase
{
    public function testItWorks(): void
    {
        self::assertTrue(true);
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

$file = 'tests/ExampleTest.php';
require_once $file;

$candidates = array_filter(get_declared_classes(), fn (string $c) => str_ends_with(strtolower($c), 'exampletest'));

foreach ($candidates as $c) {
    if (!(new ReflectionClass($c))->isSubclassOf(\PHPUnit\Framework\TestCase::class)) {
        throw new RuntimeException("{$c} does not extend PHPUnit\\Framework\\TestCase");
    }
}

Type guard

/** @phpstan-assert class-string<\PHPUnit\Framework\TestCase> $className */
function isTestCaseClass(string $className): bool
{
    return is_subclass_of($className, \PHPUnit\Framework\TestCase::class);
}

Prevention

When it happens

Trigger: Running 'phpunit src/ExampleTest.php' where ExampleTest exists in the file but is a plain class, an interface/trait consumer, extends a different base (e.g. TestCase of another framework), or extends PHPUnit Framework TestCase only indirectly via a broken parent chain.

Common situations: Renaming a class from a fixture to a test without adding the extends clause; mixing test frameworks (Codeception, Behat, custom base classes) and pointing phpunit at their files; extending a base TestCase class that was refactored away from PHPUnit\Framework\TestCase.

Related errors


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