mpociot/laravel-test-factory-helper
Generates Laravel model factories from your existing Eloquent models, so you can skip writing boilerplate test factories by hand. It inspects table columns and relationships to build realistic field definitions automatically—handy for legacy projects and fast test setup.
This is a dev-only scaffolding tool, not a runtime dependency. It introspects your migrated database schema and writes database/factories/*Factory.php files, so the fastest path is:
composer require --dev mpociot/laravel-test-factory-helper
php artisan generate:factories # every Eloquent model under app/
php artisan generate:factory "App\User" # one model
Before running anything, check three things:
doctrine/dbal against the live connection — it does not parse migrations. php artisan migrate first, and make sure .env points at the schema you actually want factories for.database/factories/ output first. Generated files are a starting point, not a deliverable. Open one and read the Faker guesses before trusting the rest.$factory->define() style output that you'll need to convert (or use a maintained fork). On Laravel 10+, doctrine/dbal version constraints can also block installation.The canonical first use case: an existing app with a large legacy schema, no factories, and tests you want to write today. Run the command against one model, commit the generated file, then tune it — that's the whole workflow.
Targeted generation over blanket generation. generate:factories walks everything under app/, which on a mature codebase produces dozens of files including models you don't test. Prefer generate:factory "App\Models\Post" per model as you actually write tests for it. It keeps diffs reviewable and forces you to look at each factory.
Generate → review → hand-tune → commit. Treat the output as a draft PR from a junior dev. The generator gets column names and rough types right and gets business semantics wrong. The productive loop is:
php artisan generate:factory "App\Models\Order"
git diff database/factories/OrderFactory.php
# edit: unique() on emails, Hash::make on passwords, realistic states
git commit -m "Add Order factory"
Never re-run blindly over tuned factories. Regeneration overwrites. Use --force deliberately, and only when you've changed the schema and want to diff the delta — git diff is your safety net, so always run it inside a clean working tree.
Treat foreign-key inference as a hint. The generator detects *_id columns and emits factory(App\User::class) calls. That's often correct and often wrong: nullable FKs, self-references (parent_id), polymorphic pairs (commentable_id + commentable_type), and columns pointing at models in non-standard namespaces all need manual correction. Polymorphic relations are not handled — write those by hand.
Layer states on top of the generated base. The generated definition is the "valid random row." Add $factory->state() blocks (or class methods on Laravel 8+) for the variants your tests actually need — ->state('expired'), ->state('admin') — rather than sprinkling overrides at every call site. The generator gives you the baseline; states are where the real value is.
Use it as a schema-diff tool during modernization. When you're porting a legacy app to Laravel and need factories for dozens of tables at once, blanket generation plus a quick pass per file beats writing them by hand. Budget roughly 2–5 minutes of correction per factory; that's still a large net win over writing from scratch.
Pair with seeders and existing test suites. Generated factories drop straight into DatabaseSeeder and existing RefreshDatabase tests. Nothing about the package is runtime-coupled, so there's zero risk to production — it only ever writes files.
Output syntax is version-locked. The package emits the pre-Laravel-8 $factory->define(Model::class, function (Faker $faker) { ... }) closure style. On Laravel 8+ this still works via the legacy shim for a while, but new code should be class-based (class UserFactory extends Factory). Plan to convert by hand or pick a fork; don't assume composer require on a modern app will even resolve given the doctrine/dbal constraint.
The DB is the source of truth, not your models. If a model sets a non-conventional $table, or uses casts/accessors that change the stored shape, the generator only sees raw columns. Set $table and run migrations before generating, or you'll get factories for the wrong table.
Type mapping is heuristic and frequently wrong on:
json / jsonb columns — often emitted as a plain string; replace with a realistic array.enum columns — no awareness of allowed values; hardcode the valid set.uuid primary/foreign keys — may not map to uuid() or may map a _id to an integer factory.password — will be a plain Faker string; wrap in Hash::make() or use bcrypt().email — collisions in bulk seeding; wrap in $faker->unique()->safeEmail.decimal/float money columns — Faker's randomFloat gives nonsense amounts; replace with realistic ranges.remember_token, email_verified_at — usually noise; delete or set to null/now().Timestamps and IDs are (mostly) skipped correctly — id, created_at, updated_at are handled by Eloquent, and deleted_at typically comes out null. Verify rather than assume, especially for models without $timestamps.
Model discovery is namespace-narrow. It scans app/ for classes extending Illuminate\Database\Eloquent\Model. Models in packages, src/, or custom PSR-4 roots outside app/ won't be found by the blanket command; use the per-model command with a fully-qualified class name instead. Abstract base models and interfaces extending Model can also trip discovery.
Publish and read the config. php artisan vendor:publish --provider="Mpociot\LaravelTestFactoryHelper\TestFactoryHelperServiceProvider" exposes namespace and Faker-provider settings. If your models live in a non-App\ namespace or you have custom Faker providers, this is the extension point — configure it before generating rather than fixing namespaces in every file afterward.
Debugging a bad run: generate a single model, inspect the file, and check php artisan generate:factory --help for the flags your installed version actually supports (--force, namespace options) — they've shifted between releases. If the command silently produces nothing, the usual causes are an unreachable DB, unmigrated tables, or models outside the scanned namespace.
Extension points beyond config are limited. There's no plugin API for custom column-to-Faker mappings; the practical extension path is forking the generator command or post-processing output with Pint/PHP-CS-Fixer plus a small script. For a tool you'll run a handful of times per project, that's usually not worth it — hand-editing the generated files is cheaper.
How can I help you explore Laravel packages today?