flarum/framework · error · RuntimeException

$modelClass must use the HasFactory trait and have a…

Error message

$modelClass must use the HasFactory trait and have a Factory class.

What it means

Flarum's integration testing TestCase builds test rows through Eloquent factories via throughFactory(); if the given model class has no `factory` method it throws a RuntimeException stating the model must use the HasFactory trait and have a Factory class. The testing helpers assume every model used in test fixtures is factory-backed. This is a developer-facing setup error, not a runtime data problem.

Solutions

  1. Add the HasFactory trait to the model and create a matching Factory class (or set the $factory class property / newFactory() override for non-standard namespaces)
  2. Point the test helper at an existing factory-backed model instead
  3. For untestable models, seed rows with plain arrays/DB inserts instead of the factory path

Example fix

// before
class Discussion extends Model {} // no factory
// after
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Discussion extends Model { use HasFactory; }
// plus
class DiscussionFactory extends Factory { protected $model = Discussion::class; public function definition() { return [/* ... */]; } }
Defensive patterns

Strategy: validation

Validate before calling

if (!method_exists($modelClass, 'factory')) { throw new \LogicException("$modelClass needs HasFactory + a Factory class before use in tests"); }

Try / catch

try { $this->prepareDatabase([...]); } catch (RuntimeException $e) { $this->fail('Add HasFactory/Factory to model: '.$e->getMessage()); }

Prevention

When it happens

Trigger: Using the TestCase's database row helpers (e.g. rowsThroughFactory / prepareDatabase with a model name) with a model class that lacks the HasFactory trait or whose factory class doesn't exist, so `$modelClass::factory()` / method_exists($modelClass, 'factory') fails.

Common situations: Custom extension models created without a corresponding Factory class; forgetting `use HasFactory` when overriding Laravel conventions; passing a non-Eloquent class or wrong class-string to the testing helper.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15). Data as JSON: /api/errors/9378bbf9ccb44c46. Report an issue: GitHub.

Appendix: source

Thrown at php-packages/testing/src/integration/TestCase.php:275

        if ($this->database()->getDriverName() === 'pgsql') {
            // PgSQL doesn't auto-increment the sequence when inserting the IDs manually.
            foreach ($tables as $table => $id) {
                $wrappedTable = $this->database()->getSchemaGrammar()->wrapTable($table);
                $seq = $this->database()->getSchemaGrammar()->wrapTable($table.'_'.$id.'_seq');
                $this->database()->statement("SELECT setval('$seq', (SELECT MAX($id) FROM $wrappedTable))");
            }

            $this->database()->statement("SET session_replication_role = 'origin'");
        }

        // And finally, turn on foreign key checks again.
        $this->database()->getSchemaBuilder()->enableForeignKeyConstraints();
    }

    protected function throughFactory(string $modelClass, array $attributes): array
    {
        if (! method_exists($modelClass, 'factory')) {
            throw new RuntimeException("$modelClass must use the HasFactory trait and have a Factory class.");
        }

        /** @var \Illuminate\Database\Eloquent\Factories\Factory $factory */
        $factory = $modelClass::factory();

        return array_map(function (mixed $value) {
            return is_array($value) ? json_encode($value) : $value;
        }, $factory->raw($attributes));
    }

    protected function rowsThroughFactory(string $modelClass, array $rows): array
    {
        return array_map(function (array $row) use ($modelClass) {
            return $this->throughFactory($modelClass, $row);
        }, $rows);
    }

    /**

View on GitHub (pinned to 4b939f6853)