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
- Point addTestSuite() at the concrete subclasses instead of the abstract base
- Add the abstract base class file to the exclusion list of your testsuite directory configuration in phpunit.xml
- 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
- Filter candidate classes through isRunnableTestClass() before addTestSuite() in custom loaders
- Keep abstract base test classes out of scanned testsuite directories (suffix/prefix conventions)
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
- Default test suite is not configured
- Test directory "%s" not found
- Test file "%s" not found
- Class %s is not a subclass of %s
- The tests aggregated by this TestSuite were already run
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/b636887a0d91701d.
Report an issue: GitHub.