{"record":{"id":"3bc333bb100f1f64","repo":"sebastianbergmann/phpunit","slug":"class-s-is-not-a-subclass-of-s","errorCode":null,"errorMessage":"Class %s is not a subclass of %s","messagePattern":"Class (.+?) is not a subclass of (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Framework/TestSuite.php","lineNumber":220,"sourceCode":"     * @param positive-int              $maxAttempts\n     *\n     * @throws Exception\n     */\n    public function addTestSuite(ReflectionClass $testClass, array $groups = [], int $numberOfRuns = 1, int $maxAttempts = 1): void\n    {\n        $className = $testClass->getName();\n\n        if ($testClass->isAbstract()) {\n            throw new Exception(\n                sprintf(\n                    'Class %s is abstract',\n                    $className,\n                ),\n            );\n        }\n\n        if (!is_subclass_of($className, TestCase::class)) {\n            throw new Exception(\n                sprintf(\n                    'Class %s is not a subclass of %s',\n                    $className,\n                    TestCase::class,\n                ),\n            );\n        }\n\n        $this->addTest(self::fromClassReflector($testClass, $groups, $numberOfRuns, $maxAttempts), $groups);\n    }\n\n    /**\n     * Wraps both <code>addTest()</code> and <code>addTestSuite</code>\n     * as well as the separate import statements for the user's convenience.\n     *\n     * If the named file cannot be read or there are no new tests that can be\n     * added, a <code>PHPUnit\\Framework\\WarningTestCase</code> will be created instead,\n     * leaving the current test run untouched.","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/TestSuite.php#L202-L238","documentation":"Thrown by TestSuite::addTestSuite() when the given class is concrete but does not extend PHPUnit\\Framework\\TestCase (checked with is_subclass_of()). TestSuite instances aggregate actual tests, so any class registered as a test suite must be a TestCase subclass.","triggerScenarios":"Calling addTestSuite(new ReflectionClass($class)) where $class is a plain helper, fixture, or data class; loading a tests directory that contains non-test classes (helpers, traits consumers, DTOs) whose files match the test file naming convention.","commonSituations":"Helper classes or support code stored in the tests directory with names like *Test.php or *TestCase.php; typos when referencing a test class name; custom runners registering arbitrary classes.","solutions":["Pass only classes that extend PHPUnit\\Framework\\TestCase to addTestSuite()","Rename support classes in the tests directory so they do not match the test suffix/prefix your loader scans (e.g. 'Helper' instead of 'Test')","Exclude the directory or file containing non-test classes in phpunit.xml"],"exampleFix":"// before\n$suite->addTestSuite(new ReflectionClass(TestDataFactory::class)); // not a TestCase\n\n// after\n$suite->addTestSuite(new ReflectionClass(UserRepositoryTest::class));","handlingStrategy":"type-guard","validationCode":"if (!is_subclass_of($className, \\PHPUnit\\Framework\\TestCase::class)) {\n    // not a test class: do not register it, log it for cleanup instead\n}","typeGuard":"function isRunnableTestClass(ReflectionClass $class): bool\n{\n    return !$class->isAbstract()\n        && $class->isSubclassOf(\\PHPUnit\\Framework\\TestCase::class);\n}","tryCatchPattern":null,"preventionTips":["Name support classes in tests/ without the Test suffix/prefix your loader matches","Validate dynamically discovered classes with is_subclass_of(..., TestCase::class) before registration"],"tags":["phpunit","test-suite","type-check","testcase"],"backgroundTag":"class-not-a-testcase","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}