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.
Architecture fit. The package is a test-time utility, not a runtime dependency. It addresses a genuine gap in Eloquent's testability: query scopes are implicit — once applied, they leave no trace in the object graph, so the only ways to verify them today are (a) asserting generated SQL and bindings, or (b) asserting result-set shape. Both are brittle: SQL assertions break on formatting and column ordering, and result assertions conflate the scope under test with data setup and other scopes. A purpose-built assertion narrows that to a single intent-revealing line in a feature test. For a codebase that leans on scopes for tenancy, soft-delete variants, publish-state, or authorization narrowing, this is a real reduction in regression risk. For a codebase with a handful of scopes, it is a convenience, not a capability.
Integration feasibility. Low effort. It is a require-dev package with no production footprint, no service provider obligations that matter outside tests, and no schema or config surface. The realistic work is an afternoon: install, read the actual API (the assertion trait/facade shape and its method names), and write two or three tests against an existing scoped model to confirm behaviour. This is a Composer-plus-one-file change on a feature branch.
Technical risk. The dominant risk is maturity, not mechanics. Eighteen stars and a single release from February 2023 means the package predates Laravel 11 and 12 and PHPUnit 10/11's attribute-based test configuration. Anything that introspects or wraps the Eloquent builder — macros, QueryExecuted listeners, builder subclassing, or scope-call interception — is coupled to internals that Laravel changes without a major-version signal. A silently no-op assertion is the worst failure mode here: a test that passes because the assertion never fires is worse than no test. Second-order risks: unknown coverage of global scopes, closure-based scopes, and nested whereHas scope application; possible coupling to a specific PHPUnit major; a single maintainer with no evident recent activity.
Key questions.
composer.json, and does the constraint actually permit our current minor?Verdict. Adopt conditionally as a dev-only convenience with a documented exit path. Given the size of the surface, a ~50-line internal helper that records applied scopes and exposes a single assertion is a credible substitute and removes the dependency risk entirely. The package is worth a pilot precisely because it validates whether the assertion is useful before we commit to owning one.
Stack fit. Native fit — a Laravel package for a Laravel application, published on Packagist, MIT-licensed. No bridging layer, no framework shims, no runtime configuration. It sits alongside existing PHPUnit/Pest tests with no interaction with application bootstrapping beyond what the test suite already does.
Migration path. There is no migration — it is additive. The decision is one of three: (1) install as-is if the pilot passes on our Laravel version; (2) fork and pin internally if the API is right but the version constraint fails; (3) reimplement internally if either of the first two stalls. All three are reversible within a sprint, since the only artefacts are test files.
Compatibility. Two constraints need verifying before merge: the Laravel major declared in the package versus our laravel/framework constraint, and the PHPUnit major expected by the assertion trait versus our test runner. If we are on Laravel 11/12 with PHPUnit 10+, a 2023-vintage package is likely to need a constraint override at minimum. Treat a composer require --dev failure as informative, not as a blocker — it is a fifteen-minute signal.
Sequencing.
where, a scope with joins or whereHas, and a global scope. Deliberately break one scope to prove the assertion fails — a green assertion that cannot go red is worthless.Maintenance. Negligible runtime maintenance; the cost is entirely in upgrade cycles. Expect it to break or silently degrade at the next Laravel or PHPUnit major. Budget a half-day per major upgrade to verify or replace — and, critically, verify by deliberately failing the assertion, not merely by observing green CI.
Support. No vendor, no SLA, one maintainer with a stale release. Internal ownership must be explicit: name the engineer who owns the fork or replacement decision. Do not let it spread to more than a handful of test files until an owner is assigned, because breadth of usage is what converts a small dependency into a migration project.
Scaling. Scales fine mechanically — it is a test-only library with no I/O, no network, and no state outside the test process. The scaling concern is organizational: as more teams write scope assertions, the dependency becomes load-bearing for test confidence, and its staleness becomes a shared risk. Cap the blast radius by keeping usage concentrated in the model/service tests that actually guard scopes, and prefer a small internal helper if adoption exceeds a few dozen call sites.
Failure modes. Ranked by severity:
Ramp-up. Very low for engineers already fluent in Laravel testing — under an hour, mostly reading the API. The real onboarding cost is conceptual: teams must learn when a scope assertion is the right tool versus when asserting query results is clearer. Without that guidance, expect inconsistent usage and a spread of near-duplicate testing styles. One paragraph in the testing docs and one exemplar test file covers it.
How can I help you explore Laravel packages today?