tkaratug/eloquent-scope-assertion
A lightweight Laravel testing helper for asserting that Eloquent model scopes apply the expected query constraints. Write expressive, readable tests for your local scopes without brittle SQL comparisons, and keep your query logic verified and maintainable.
The package is a dev-only testing helper: it lets you assert that an Eloquent local scope was applied to a query without necessarily executing the query against a database. That makes it useful for testing scope logic in isolation, especially scopes that are conditional, composable, or buried inside repository/query-builder methods.
Minimal setup:
composer require --dev tkaratug/eloquent-scope-assertion
The package supports Laravel package auto-discovery, so no manual service provider registration is normally required. Confirm the exact assertion method names and supported Laravel/PHP versions in the repository README and composer.json before adopting it — the last tagged release is from February 2023, so verify compatibility with your Laravel version (10/11/12) and PHP version.
Where to look first:
tests/ directory — the best source of truth for how scopes are detected (e.g., whether it inspects $query->getQuery()->scopes or the generated SQL).scope* methods — identify which ones you want to pin down with assertions.First use case: you have a User::scopeActive() or Post::scopePublished() and want a fast unit test that a query built by some service/repository actually applied it:
public function test_active_users_query_applies_scope(): void
{
$query = User::query()->active();
// Use the package's assertion helper here; check README for exact signature,
// e.g. $this->assertScope(...) / assertScopeApplied(...).
$this->assertScope('active', $query);
// Sanity check while learning the package:
$this->assertStringContainsString('active = 1', $query->toSql());
}
Start by pairing the package assertion with a raw toSql()/getBindings() check until you trust what the assertion actually verifies.
Pin scope application in repository/service tests. The highest-value pattern is asserting that a query builder returned by your application code has the expected scopes attached, without needing seeded data:
public function test_repository_applies_visible_scope(): void
{
$query = (new PostRepository())->visible();
$this->assertScope('visible', $query);
}
Test conditional scopes with data providers. Scopes that branch on input are ideal targets. Assert the scope is present when the condition is true and absent when false:
/**
* @dataProvider roleProvider
*/
public function test_scope_is_conditional(string $role, bool $expected): void
{
$query = User::query()->forRole($role);
$expected
? $this->assertScope('forRole', $query)
: $this->assertScopeNotApplied('forRole', $query); // verify exact name in README
}
Pair “scope applied” with behavioral tests. Use the scope assertion as a cheap contract test, then keep one database-backed feature test that proves the scope returns the right rows. The assertion package proves wiring; the feature test proves behavior.
Use it in Pest. The assertion should work anywhere Laravel's TestCase assertions are available:
it('applies the published scope', function () {
$query = Post::query()->published();
$this->assertScope('published', $query);
});
Guard scope composition. If a scope delegates to another scope (scopePublished() calling $query->whereNotNull('published_at') plus ->active()), assertions let you verify the outer scope is present and, separately, that composition chains produce the expected builder state.
CI integration. Because the package avoids database round-trips for the scope check itself, these tests run fast in a pure unit suite. Keep them in a separate tests/Unit/Scopes directory so they don't require RefreshDatabase.
Integration tip: wrap the assertion in your own base test helper (e.g., assertQueryHasScope($query, 'active')) so a future API change or package replacement touches one file instead of hundreds.
scopeFoo). Global scopes, $query->withGlobalScope(), and relationship constraints are a different mechanism — don't expect assertions to cover them.where clauses may still count as “applied.” Confirm this behavior in the package tests before relying on it for negative assertions.scopeOfType($query, $type) is “applied,” the assertion generally won't prove the value of $type was bound correctly. Add an explicit getBindings() or toSql() assertion for scopes with parameters.$query->toSql() and $query->getBindings(), and inspect $query->getQuery()->scopes (or removedScopes) to see what the builder actually holds. Most failures are “scope never called” rather than “assertion helper broken.”->get() call are not the same as explicitly chaining ->active(). If your code applies scopes deep inside a method, extract and return the builder so the test can inspect it.composer.json for extra.laravel.providers/aliases and the README for any publishable config. If auto-discovery is disabled in your app, register the provider manually in the test environment.TestCase, you can compose your own assertions around it. If it doesn't, a 10-line custom assertion using getQuery()->scopes gives you the same value with zero dependency risk:protected function assertQueryHasScope(string $scope, Builder $query): void
{
$this->assertContains($scope, $query->getQuery()->scopes);
}
How can I help you explore Laravel packages today?