codenco-dev/eloquent-model-tester
Laravel dev-only helper to test Eloquent models: verify table structure/columns, fillable vs guarded attributes, and model relationships. Works with PHPUnit and model factories, integrates easily in your model test classes.
Installation
composer require codenco-dev/eloquent-model-tester --dev
Generate Test File
php artisan make:test Models/UserTest
Add Trait
In your test file, include the HasModelTester trait:
use CodencoDev\EloquentModelTester\HasModelTester;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase, HasModelTester;
}
First Test Test basic model structure:
public function test_model_structure()
{
$this->modelTestable(User::class)
->assertHasColumns(['id', 'name', 'email'])
->assertHasTimestampsColumns();
}
$this->modelTestable(User::class)
->assertHasColumns(['id', 'name', 'email']);
$this->modelTestable(User::class)
->assertHasOnlyColumns(['id', 'name', 'email', 'created_at', 'updated_at']);
$this->modelTestable(User::class)
->assertHasColumnsInFillable(['name', 'email']);
$this->modelTestable(User::class)
->assertHasOnlyColumnsInFillable(['name', 'email']);
$this->modelTestable(User::class)
->assertHasColumnsInGuarded(['password']);
$this->modelTestable(User::class)
->assertHasHasOneRelation(Phone::class);
$this->modelTestable(Phone::class)
->assertHasBelongsToRelation(User::class);
$this->modelTestable(User::class)
->assertHasManyToManyRelation(Role::class);
$this->modelTestable(Customer::class)
->assertHasBelongsToRelation(Category::class, 'category', 'category_id');
$this->modelTestable(Post::class)
->assertHasHasManyMorphRelation(Comment::class, 'comments');
$this->modelTestable(User::class)
->assertHasMorphOneRelation(Image::class, 'avatar');
$this->tableTestable('role_user')
->assertHasColumns(['user_id', 'role_id', 'created_at']);
$this->modelTestable(User::class)
->assertHasScope('popular');
Group Tests by Model
Organize tests in tests/Feature/Models/ with a ModelNameTest file per model.
Leverage TestCase Base Class
Extend TestCase with HasModelTester and RefreshDatabase in tests/TestCase.php to avoid repetition.
Combine Assertions Chain assertions for comprehensive validation:
$this->modelTestable(User::class)
->assertHasColumns(['id', 'name', 'email'])
->assertHasTimestampsColumns()
->assertHasHasOneRelation(Phone::class)
->assertHasScope('popular');
Use in CI/CD Integrate into your test suite to catch schema/relation changes early.
Missing Timestamps in Strict Checks
assertHasOnlyColumns() requires explicit created_at/updated_at if they exist in the DB.Case Sensitivity in Column Names
Soft Deletes Require Trait
assertHasSoftDeleteTimestampColumns() only works if the model uses SoftDeletes.use SoftDeletes; to your model.Many-to-Many Pivot Assumptions
model1_model2). Override with custom names if needed:
$this->modelTestable(User::class)
->assertHasManyToManyRelation(Role::class, 'user_roles');
Morph Relations Need Exact Keys
morphMap or getMorphClass() logic.Inspect Schema Dynamically
Use Laravel’s Schema::getColumnListing() to verify column names:
dd(\Schema::getColumnListing('users'));
Check Relation Definitions
For custom relations, verify the model’s belongsTo, hasOne, etc., definitions match the test assertions.
Enable Database Logging
Add to config/logging.php to debug schema issues:
'channels' => [
'database' => [
'driver' => 'database',
'table' => 'log',
'level' => 'debug',
],
],
Custom Assertions Extend the trait to add model-specific assertions:
trait CustomModelTester
{
public function assertHasCustomField(string $field)
{
return $this->assertDatabaseHas('users', [$field => 'expected_value']);
}
}
Override Default Keys For non-standard foreign keys, pass explicit keys to relation methods:
$this->modelTestable(Customer::class)
->assertHasBelongsToRelation(Category::class, 'category', 'custom_category_id');
Combine with Factories Use factories to seed test data for relation tests:
public function test_has_one_relation()
{
$user = User::factory()->create();
Phone::factory()->create(['user_id' => $user->id]);
$this->modelTestable(User::class)
->assertHasHasOneRelation(Phone::class);
}
Skip Tests Conditionally
Use Laravel’s skipIf() or skipUnless() to conditionally run tests (e.g., for optional features):
public function test_optional_feature()
{
$this->skipUnless(config('features.enable_optional_feature'));
$this->modelTestable(Model::class)
->assertHasScope('optional_scope');
}
Mock Database for Complex Scenarios
Use DatabaseMigrations or DatabaseTransactions for tests requiring complex state:
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ComplexRelationTest extends TestCase
{
use DatabaseMigrations, HasModelTester;
public function test_has_many_through()
{
// Setup complex relations...
$this->modelTestable(Customer::class)
->assertHasHasManyThroughRelation(Order::class, Location::class);
}
}
How can I help you explore Laravel packages today?