sebastianbergmann/phpunit · error · Exception

Class %s is abstract

Error message

Class %s is abstract

What it means

Thrown by TestSuite::addTestSuite() when the ReflectionClass passed refers to an abstract class. An abstract class cannot be instantiated or run as a test suite, so PHPUnit rejects it explicitly before trying to collect its test methods.

Source

Thrown at src/Framework/TestSuite.php:211

        }
    }

    /**
     * Adds the tests from the given class to the suite.
     *
     * @param ReflectionClass<TestCase> $testClass
     * @param list<non-empty-string>    $groups
     * @param positive-int              $numberOfRuns
     * @param positive-int              $maxAttempts
     *
     * @throws Exception
     */
    public function addTestSuite(ReflectionClass $testClass, array $groups = [], int $numberOfRuns = 1, int $maxAttempts = 1): void
    {
        $className = $testClass->getName();

        if ($testClass->isAbstract()) {
            throw new Exception(
                sprintf(
                    'Class %s is abstract',
                    $className,
                ),
            );
        }

        if (!is_subclass_of($className, TestCase::class)) {
            throw new Exception(
                sprintf(
                    'Class %s is not a subclass of %s',
                    $className,
                    TestCase::class,
                ),
            );
        }

        $this->addTest(self::fromClassReflector($testClass, $groups, $numberOfRuns, $maxAttempts), $groups);

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Point addTestSuite() at the concrete subclasses instead of the abstract base
  2. Add the abstract base class file to the exclusion list of your testsuite directory configuration in phpunit.xml
  3. Rename/move abstract test case classes outside the scanned tests directory (e.g. to src/ or a supports/ folder)

Example fix

// before
$suite->addTestSuite(new ReflectionClass(AbstractApiTestCase::class)); // abstract base

// after
$suite->addTestSuite(new ReflectionClass(UserApiTest::class)); // concrete child
Defensive patterns

Strategy: type-guard

Validate before calling

$class = new ReflectionClass($candidate);
if ($class->isAbstract()) {
    // register concrete children: $class->isAbstract() ? skip : addTestSuite($class)
}

Type guard

function isRunnableTestClass(ReflectionClass $class): bool
{
    return !$class->isAbstract()
        && $class->isSubclassOf(\PHPUnit\Framework\TestCase::class);
}

Prevention

When it happens

Trigger: Calling $suite->addTestSuite(new ReflectionClass($someAbstractClass)) directly, or pointing the CLI/loader at a file whose class is abstract and extends TestCase (common for base test classes holding shared fixtures).

Common situations: An abstract TestCase base class (e.g. AbstractApiTestCase) living in a directory the test loader scans; passing a base class name to addTestSuite() by mistake.

Related errors


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