mozex/laravel-test-lanes
A Laravel package for splitting your test suite into balanced lanes so PHPUnit or Pest tests can run in parallel across CI jobs, reducing total test time while keeping lane workloads predictable and easy to configure.
mozex/laravel-test-lanes fans a Laravel test suite out across parallel "lanes", each lane getting its own database, so you trade a single slow php artisan test for N workers that don't collide on fixtures.
composer require --dev mozex/laravel-test-lanes.max_connections are all set there. This is the first file to look at when anything behaves oddly..env.testing (e.g. DB_DATABASE=app_testing). Lane databases are derived from that base name, so it must be a name you're happy to have created and dropped automatically.composer test script so the team gets parallelism by default.Keep php artisan test-lanes:cleanup in your muscle memory from day one — after a crashed or Ctrl-C'd run it is the difference between a clean slate and a pile of orphaned app_testing_* databases.
Standard local workflow. Run the lanes command in place of php artisan test for the full suite; drop back to plain PHPUnit/Pest with --filter for tight red-green loops, since lane startup (migrate + seed per lane) is the cost you're amortising.
CI parallelism without a matrix. One CI job, lanes set high enough to saturate the runner, and a real database service. Size the connection pool from the config's max_connections, and count every connection each worker opens — application, migration/DDL, and any extra connections your tests touch. Since 1.2.1 the guidance is to count all of them, not just the traditional two; undercounting shows up as intermittent "too many connections" on the last few lanes.
Cleanup as a guarded CI step. Run cleanup outside the test process, always with the database name passed explicitly:
DB_DATABASE=app_testing php artisan test-lanes:cleanup
From a shell, the command reads whatever .env says; if that's your dev database the lane lookup finds nothing and you get a no-op that looks successful.
Per-connection cleanup. Use test-lanes:cleanup --connection=... when lanes exist on a non-default connection — but only as a step in which no tests are running. Lane locking is tracked against the default connection's database only, so a --connection cleanup issued mid-run can drop lane databases on another connection while workers are still using them.
Migrations and seeding. Every lane pays the migrate/seed cost, so keep it cheap: schema dumps (php artisan schema:dump) beat a long migration chain, and seeding should be per-lane rather than global. Anything that assumes a single database name in config or in a test helper needs to read the active connection instead.
Test-impact caches. If you use Pest's Tia (or any per-checkout impact/result cache), it is shared across lanes and across concurrent runs — that is the deliberate trade in this package. Treat it as an integration point: decide whether the shared cache is helping you (faster lane startup) or hurting you (stale selection), and wire cache priming/clearing into your CI script accordingly.
cleanup from a shell finds no lanes. It reads DB_DATABASE from .env, not from the lane registry. Always pass DB_DATABASE=app_testing (or your testing name) when invoking it by hand or from a CI step.--connection cleanup is not concurrency-safe. Only run it with the test process stopped. If you need cleanup during a run, clean the default connection only.max_connections is the classic footgun. Under-provisioning produces flaky connection failures that only appear at higher lane counts or on cold caches. Over-provisioning wastes DB headroom. Measure what a worker actually opens.app_testing_*) and compare against what cleanup reports; a mismatch means the command is pointed at the wrong base database.config/test-lanes.php rather than environment sprawl, so local and CI stay aligned. Pin the version in composer.json and re-read the config after minor releases — 1.2.1 changed documented guidance on connection counting and cleanup invocation, and those docs live next to the behaviour you're relying on.How can I help you explore Laravel packages today?