spatie/pest-plugin-route-testing
Easily test every GET route in your Laravel app with Pest. Assert all routes return successful responses, include or exclude patterns, and bind models for routes with route model bindings.
Architecture fit. The package occupies a single, well-bounded layer: the test suite. It is a --dev Composer dependency that reads the application's route table (via route:list since the 0.0.3 refactor), fans the results into Pest data providers, dispatches each route through Laravel's standard HTTP test harness, and forwards assertions to TestResponse. There is no service provider in the production boot path, no migrations, no configuration surface, no runtime dependency, and no data model. Coupling is limited to two public Laravel contracts — the route listing and TestResponse assertions — both of which are stable and versioned. Reversibility is a single composer remove commit plus deletion of one test file. For a TPM this is about as low-stakes an addition to a roadmap as exists: it is a testing ergonomics improvement, not a platform decision, and should be scoped and resourced accordingly.
Integration feasibility. Effort is low (estimated 1–2 engineer-days including CI wiring and triage of the first run), but there is one hard gate: the application must use Pest. This is a Pest plugin, not a PHPUnit plugin. The package version is therefore not a free choice — it is determined by the stack:
Teams on older PHP or Laravel land on 1.1.x and should plan on that branch being effectively frozen. Teams with no Pest at all face a framework migration (Pest is a PHPUnit superset, so the path is incremental, but it touches every existing test) — that is a separate initiative and should not be smuggled in under this package. The cheap alternative for non-Pest shops is a hand-rolled PHPUnit data provider over route:list, roughly 50–100 lines, functionally equivalent for the smoke-test use case.
Technical risk. Low code risk, moderate perception risk. The library is small, MIT-licensed, and forkable by a single engineer if Spatie archives it. The genuine risk is that the initiative delivers a green badge that means less than stakeholders believe:
user/{user}-style paths. On a typical CRUD app the skipped set can be the majority of the interesting routes. The skip report mitigates but does not eliminate this; the mitigation is a documented, named skipped-route baseline reviewed each sprint.assertSuccessful() proves the route returns 200 — not that it returns the right page, the right authorization boundary, or the right data. A 200 from a mis-rendered view or a leaked admin page passes. This is a smoke test; position it internally as such and resist pressure to count it as coverage.setUp() + actingAs(), protected routes return 302/403 and will fail assertSuccessful(); teams often "fix" this by excluding them, silently removing the most security-relevant routes from the check.Key questions. (1) Which Pest/PHP/Laravel triple are we on, and therefore which package line do we pin? (2) What fraction of our GET routes are skipped for missing bindings, and who owns that list? (3) Does the first run land in CI as blocking or advisory? (4) Who triages initial failures, and is that budgeted? (5) Do we accept a non-Pest fallback, or is Pest adoption a prerequisite deliverable? (6) What is our internal definition of "done" — package installed, or skipped-route baseline driven below an agreed threshold?
Stack fit. The package slots into any Pest-based Laravel repo with zero application changes. The practical prerequisite chain is: Composer dev dependency → Pest present → route table resolvable in the test environment. That last point matters more than it appears: routes gated behind feature flags, environment guards, or module providers that do not register in testing will simply be absent from the run and will not announce themselves. Confirm early that route:list in CI shows a route count comparable to production, and treat a wide gap as a finding in its own right.
Migration path. Nothing to migrate — this is additive, dev-only, and removable. The two real paths are:
routeTesting('all GET routes')->assertSuccessful(), run locally, triage, then wire into CI. 1–2 days.Compatibility. Two axes to pin explicitly in composer.json: the package line (1.2.x vs 1.1.x) and the Pest major. 1.2.0's jump to PHP 8.4 / Testbench ^11 means an upgrade of the package is effectively an upgrade of the runtime and the framework test harness — it will not be a routine patch bump and should be scheduled as a platform task, not a dependency chore. Note also that 1.1.x has a history of small compatibility fixes (named-parameter handling in 1.1.2, TestResponse macro dispatch in 1.1.3, PHPStan resolution of TestResponse methods in 1.2.1), which tells you the surface is thin but not inert. Watch the 1.1.x branch: with 1.2.0 pushing to PHP 8.4, backports there are unlikely.
Sequencing. (1) Confirm stack triple and pin the version. (2) Author the baseline test locally and capture the full pass/fail/skip report — this is the actual deliverable of phase one, not the green build. (3) Triage failures into genuine bugs, environment gaps, and pre-existing known-broken routes; route the first category into the backlog as discovered defects, which is where the package usually pays for itself immediately. (4) Add include()/exclude() scoping to carve out deliberately untestable surfaces (_debugbar, telescope, horizon, webhook receivers), documenting each exclusion with a reason. (5) Add setUp() + actingAs() blocks per auth tier so protected routes are tested rather than excluded. (6) Add bind() fixtures for the highest-traffic model-bound routes. (7) Only then promote the job from advisory to blocking in CI.
Maintenance. Near-zero for the dependency, non-zero for the test file, and the non-zero part is where TPM attention belongs. Every new route with a model binding adds a skip; every new auth tier adds a setUp() block; every intentional exclusion adds a line that must be re-justified when the route changes. Budget roughly a half-day per quarter for baseline hygiene, and assign a named owner. Upstream, the project is a single-maintainer Spatie package with 141 stars and 0 dependents — small, healthy, but not load-bearing for anyone else. If it is abandoned, forking or inlining the ~50–100 line equivalent is a day's work. Do not let it acquire the status of critical infrastructure in planning documents.
Support. Community-only, MIT, no SLA, no vendor relationship to manage. Internal support burden is entirely "who understands the route baseline and the exclusion list." That is a single-engineer bus factor and should be written into the test file as comments so it survives turnover. Escalation for a package bug is a GitHub issue with no timeline; the realistic response is a fork.
Scaling. Two scaling behaviours to anticipate. First, route count: each route is a data-provider case executing a full HTTP kernel boot and DB transaction. At a few hundred routes this is minutes; at a few thousand it will dominate CI wall-clock and needs sharding or a --parallel strategy. Model the suite growth against current CI duration before making it blocking, because a blocking job that adds ten minutes to every PR gets disabled by engineers within a month regardless of policy. Second, fixture cost: setUp() blocks creating factories per auth tier multiply across every route in scope. Use shared fixtures or RefreshDatabase with seeding rather than per-case factories.
Failure modes. Ranked by likelihood and damage:
ignoreRoutesWithMissingBindings() deliberately not used, and a CI assertion or periodic report on skipped-route count with a threshold.exclude() and review of the exclusion list at each planning cycle.Queue::fake(), Http::fake(), and RefreshDatabase in the base test case; expect this to be the bulk of week-one triage.Ramp-up. Effectively flat for anyone who has written a Pest test: the DSL is four methods (include, exclude, bind, setUp) plus any TestResponse assertion. First-value time is measured in hours, and the first run typically surfaces one or two real 500s, which is the fastest way to buy credibility for the initiative. The learning curve is not the API — it is the discipline of maintaining the skip baseline and resisting exclusion sprawl. Frame the rollout around that discipline, not around the install, and set the success metric accordingly: not "package installed and green," but "skipped-route percentage trending down quarter over quarter, with every exclusion carrying a written justification."
How can I help you explore Laravel packages today?