Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Model Tester Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require codenco-dev/eloquent-model-tester --dev
    
  2. Generate Test File:

    php artisan make:test Models/UserTest
    
  3. Basic Setup:

    use App\Models\User;
    use CodencoDev\EloquentModelTester\HasModelTester;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    use Tests\TestCase;
    
    class UserTest extends TestCase
    {
        use RefreshDatabase, HasModelTester;
    
        public function test_model_structure()
        {
            $this->modelTestable(User::class)
                ->assertHasColumns(['id', 'name', 'email']);
        }
    }
    

First Use Case

Verify a model's database schema and fillable/guarded attributes:

public function test_model_structure()
{
    $this->modelTestable(User::class)
        ->assertHasColumns(['id', 'name', 'email', 'created_at', 'updated_at'])
        ->assertHasOnlyColumnsInFillable(['name', 'email'])
        ->assertHasTimestampsColumns();
}

Implementation Patterns

Common Workflows

1. Schema Validation

// Basic column check
$this->modelTestable(User::class)
    ->assertHasColumns(['id', 'name', 'email']);

// Strict column check (no extra columns allowed)
->assertHasOnlyColumns(['id', 'name', 'email', 'created_at', 'updated_at']);

// Soft deletes
->assertHasSoftDeleteTimestampColumns();

2. Fillable/Guarded Validation

// Check if columns are in fillable
->assertHasColumnsInFillable(['name', 'email']);

// Strict fillable check (only these should be fillable)
->assertHasOnlyColumnsInFillable(['name', 'email']);

// Check for guarded fields
->assertHasColumnsInGuarded(['password']);

// Strict guarded check
->assertHasOnlyColumnsInGuarded(['password']);

// Ensure no overlap between fillable and guarded
->assertNoGuardedAndFillableFields();

3. Relation Testing

// One-to-One
->assertHasHasOneRelation(Phone::class);

// One-to-Many
->assertHasHasManyRelation(Order::class);

// Many-to-Many
->assertHasManyToManyRelation(Role::class);

// Custom keys for relations
->assertHasBelongsToRelation(Category::class, 'category', 'category_id');

// Morph relations
->assertHasHasManyMorphRelation(Comment::class, 'comments');

4. Chaining Assertions

$this->modelTestable(Customer::class)
    ->assertHasBelongsToRelation(Category::class)
    ->assertHasBelongsToRelation(OtherModel::class)
    ->assertHasHasManyRelation(Order::class);

5. Table-Level Testing (Pivot Tables)

$this->tableTestable('role_user')
    ->assertHasColumns(['user_id', 'role_id', 'created_at']);

6. Scope Testing

->assertHasScope('active')
->assertHasScope('popular');

Integration Tips

1. Centralize RefreshDatabase

Add RefreshDatabase to tests/TestCase.php to avoid repetition:

use Illuminate\Foundation\Testing\RefreshDatabase;

class TestCase extends BaseTestCase
{
    use RefreshDatabase;
    // ...
}

2. Group Tests by Model

Organize tests in tests/Feature/Models/:

tests/
├── Feature/
│   ├── Models/
│   │   ├── UserTest.php
│   │   ├── PostTest.php
│   │   └── ...

3. Combine with Factory Testing

Use factories to ensure data integrity before schema tests:

public function test_model_with_factory()
{
    User::factory()->create(['name' => 'Test User']);

    $this->modelTestable(User::class)
        ->assertHasColumns(['id', 'name', 'email']);
}

4. Extend for Custom Logic

Create a base test class for shared assertions:

abstract class ModelTestCase extends TestCase
{
    use HasModelTester;

    protected function assertModelStructure($model, array $columns, array $fillable = [])
    {
        $this->modelTestable($model)
            ->assertHasOnlyColumns($columns)
            ->assertHasOnlyColumnsInFillable($fillable);
    }
}

5. CI/CD Integration

Add tests to your CI pipeline (e.g., GitHub Actions) to catch schema drift early:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: composer install
      - run: php artisan test --filter="*Test"

Gotchas and Tips

Pitfalls

1. Missing Timestamps in assertHasOnlyColumns

  • Issue: Forgetting to include created_at/updated_at in assertHasOnlyColumns causes false failures.
  • Fix: Always include timestamps if they exist in the table:
    ->assertHasOnlyColumns(['id', 'name', 'email', 'created_at', 'updated_at'])
    

2. Case Sensitivity in Column Names

  • Issue: Database column names might differ in case (e.g., ID vs id).
  • Fix: Normalize case in assertions or use raw SQL:
    ->assertHasColumns(array_map('strtolower', ['ID', 'Name']))
    

3. Soft Deletes Without Trait

  • Issue: assertHasSoftDeleteTimestampColumns fails if SoftDeletes trait isn’t used.
  • Fix: Ensure the trait is imported:
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    class User extends Model
    {
        use SoftDeletes;
    }
    

4. Custom Keys in Relations

  • Issue: Non-standard relation keys (e.g., user_id vs author_id) break assertions.
  • Fix: Explicitly pass keys:
    ->assertHasBelongsToRelation(Author::class, 'author', 'author_id')
    

5. ManyToMany Pivot Table Mismatches

  • Issue: Pivot table names or columns don’t match the relation definition.
  • Fix: Verify pivot table structure separately:
    $this->tableTestable('role_user')
        ->assertHasColumns(['user_id', 'role_id', 'created_at']);
    

6. Scope Naming Conflicts

  • Issue: Scopes with similar names (e.g., active vs isActive) cause false negatives.
  • Fix: Use exact scope names:
    ->assertHasScope('isActive'); // Not 'active'
    

Debugging Tips

1. Inspect Schema Directly

Use Laravel’s schema builder to debug column names:

public function test_schema_debug()
{
    $columns = Schema::getColumnListing('users');
    dd($columns); // Inspect actual column names
}

2. Log Assertion Failures

Extend the trait to log failures:

trait DebugModelTester
{
    public function modelTestable($model)
    {
        $tester = parent::modelTestable($model);
        $tester->setHandler(function ($message, $context) {
            \Log::error($message, $context);
        });
        return $tester;
    }
}

3. Partial Assertions

Test subsets of columns/relations incrementally:

// Test fillable first
$this->modelTestable(User::class)
    ->assertHasColumnsInFillable(['name']);

// Then add more
->assertHasColumnsInFillable(['email']);

4. Database State

Ensure RefreshDatabase is enabled for tests that modify the schema:

use RefreshDatabase; // Add to test class

5. Migration Conflicts

  • Issue: Schema tests fail after migrations but pass locally.
  • Fix: Run migrations fresh in CI:
    php artisan migrate:fresh --env=testing
    

Extension Points

1. Custom Assertions

Extend the tester for project-specific rules:

use CodencoDev\EloquentModelTester\Testable;

class CustomTester extends Testable
{
    public function assertHasCustomColumn($column)
    {
        return $this->seeInDatabase($this->model->getTable(), $column, function ($query) {
            return $query->limit(1);
        });
    }
}

2. Dynamic Relation Testing

Generate relation tests dynamically:

public function test_all_relations()
{
    $relations = (new User)->getRelations();
    foreach ($relations as $relation) {
        $this->modelTestable(User::class)
            ->assertHasRelation($relation);
    }
}

3. Integration with Laravel Pints

Combine with laravel/pint for schema + code style checks:

composer require laravel/pint --dev
./vendor/bin/pint
php artisan test --filter="*Test"

**4. Pre-Commit Hooks

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky