Installation:
composer require --dev sofa/eloquent-testsuite
Add the trait to your test class:
use Sofa\EloquentTestsuite\EloquentSuite;
First Use Case:
Test a simple belongsTo relation:
class UserTest extends TestCase
{
use EloquentSuite;
/** @test */
public function user_has_post()
{
$user = $this->createRelationMock(User::class, 'belongsTo', Post::class);
$this->assertRelation('belongsTo', $user->post());
}
}
Key Files to Explore:
tests/Feature/Models/ – Your model tests.src/Sofa/EloquentTestsuite/EloquentSuite.php – Core trait methods.// Mock a belongsTo relation
$user = $this->createRelationMock(User::class, 'belongsTo', Post::class);
$this->assertRelation('belongsTo', $user->post());
// Mock a hasMany relation with query constraints
[$user, $relation] = $this->createRelationChainMock(User::class, 'hasMany', Post::class);
$relation->shouldReceive('whereActive')->once()->andReturnSelf();
$this->assertRelation('hasMany', $user->posts());
// Assert a scope exists and filters correctly
$this->assertScopeFilters(User::class, 'active', 'is_active', true);
$this->assertScopeFilters(User::class, 'archived', 'is_active', false);
// Test scope chaining
$this->assertScopeFilters(User::class, 'activeArchived', 'is_active', true)
->assertScopeFilters(User::class, 'activeArchived', 'archived', true);
// Mock event listeners
$user = $this->createModelMock(User::class);
$user->shouldReceive('fireModelEvent')->with('creating')->once();
$this->createModel(User::class);
$user = $this->createModelMock(User::class);
$user->shouldReceive('getFullNameAttribute')->once()->andReturn('John Doe');
$this->assertAttribute('full_name', 'John Doe', $user);
// Combine with Laravel factories for realistic data
$user = User::factory()->create();
$this->assertDatabaseHas('users', ['id' => $user->id]);
Use createRelationChainMock for nested relations:
[$user, $posts, $comments] = $this->createRelationChainMock(
User::class,
'hasMany',
Post::class,
'hasMany',
Comment::class
);
$this->createRelationMock(
User::class,
'morphTo',
[Post::class, Comment::class]
);
$this->assertQueryConstraints(
User::where('active', true)->get(),
['active' => true]
);
$user = $this->createModelMock(User::class);
$user->shouldReceive('getAttributeFromArray')->with('email')->andReturn('test@example.com');
$this->assertAttribute('email', 'test@example.com', $user);
Mocking Static Methods:
The package doesn’t natively support static methods (e.g., Model::boot()). Use Mockery directly:
$mock = Mockery::mock(User::class . '::class');
$mock->shouldReceive('boot')->once();
Database Transactions: Ensure tests run in a transaction to avoid side effects:
public function setUp(): void
{
parent::setUp();
$this->beginDatabaseTransaction();
}
Time-Sensitive Scopes:
Mock Carbon or now() in scopes:
$mock = Mockery::mock('overload:' . Carbon::class);
$mock->shouldReceive('now')->andReturn(Carbon::create(2023, 1, 1));
Laravel 8+ Compatibility: The package was last updated in 2020. For Laravel 8/9, manually patch:
// In phpunit.xml
<php>
<env name="DB_CONNECTION" value="sqlite_memory"/>
</php>
Inspect Mocks:
Use dd($this->createRelationMock(...)) to verify mock setup.
Assertion Failures: Enable detailed error output:
$this->assertRelation('belongsTo', $user->post(), true); // Verbose mode
Scope Testing:
If assertScopeFilters fails, manually verify the query:
$query = User::active()->toSql();
$this->assertStringContainsString('is_active = 1', $query);
Custom Assertions: Extend the trait to add domain-specific assertions:
trait CustomAssertions
{
protected function assertCustomValidation($model, $rules)
{
// Custom logic
}
}
Mocking Observers: Use Mockery to stub observer methods:
$observer = Mockery::mock(UserObserver::class);
$observer->shouldReceive('saved')->once();
Testing API Resources:
Combine with Laravel\Sanctum or Laravel\Passport mocks:
$user = $this->createModelMock(User::class);
$user->shouldReceive('toArray')->andReturn(['id' => 1]);
Performance Testing:
Use Benchmark trait for timing:
use Sofa\EloquentTestsuite\Benchmark;
$this->benchmark('Model hydration', function () {
User::find(1);
});
How can I help you explore Laravel packages today?