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
- 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)
- Point the test helper at an existing factory-backed model instead
- 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
- Give every extension model a Factory class from the start
- Use `php artisan make:factory` when creating models
- Verify new models compile by running a trivial factory-based test
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)