PHPackages                             mozex/laravel-test-lanes - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. mozex/laravel-test-lanes

ActiveLibrary[Testing &amp; Quality](/categories/testing)

mozex/laravel-test-lanes
========================

Give every concurrent Laravel test run its own databases by claiming a machine-unique lane for each process

1.1.1(today)18↑2900%[1 PRs](https://github.com/mozex/laravel-test-lanes/pulls)MITPHPPHP ^8.2.0CI passing

Since Aug 1Pushed today1 watchersCompare

[ Source](https://github.com/mozex/laravel-test-lanes)[ Packagist](https://packagist.org/packages/mozex/laravel-test-lanes)[ Docs](https://github.com/mozex/laravel-test-lanes)[ GitHub Sponsors](https://github.com/mozex)[ RSS](/packages/mozex-laravel-test-lanes/feed)WikiDiscussions main Synced today

READMEChangelog (3)Dependencies (16)Versions (4)Used By (0)

Laravel Test Lanes
==================

[](#laravel-test-lanes)

[![Latest Version on Packagist](https://camo.githubusercontent.com/3d11c0529e6cc5b0f57c9707cf82c7544043ca844c54c4fef83d40bed60c4461/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6f7a65782f6c61726176656c2d746573742d6c616e65732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-test-lanes)[![GitHub Checks Workflow Status](https://camo.githubusercontent.com/2844157740351c3cbd338aff3247fd7daa255d1c2b562ce71b97ae356a1e4fe7/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6d6f7a65782f6c61726176656c2d746573742d6c616e65732f636865636b732e796d6c3f6272616e63683d6d61696e266c6162656c3d636865636b73267374796c653d666c61742d737175617265)](https://github.com/mozex/laravel-test-lanes/actions/workflows/checks.yml)[![Docs](https://camo.githubusercontent.com/6bae6cab4895bc9b3768db4e7f44e2640da7b03da9635f43f34a1ea306f23e0e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63732d6d6f7a65782e6465762d3130423938313f7374796c653d666c61742d737175617265)](https://mozex.dev/docs/laravel-test-lanes/v1)[![License](https://camo.githubusercontent.com/58de2eaabca6f2304af690de2701ed2bba42a585f2df271fb1ad6e80f4521d19/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d6f7a65782f6c61726176656c2d746573742d6c616e65733f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-test-lanes)[![Total Downloads](https://camo.githubusercontent.com/b26a6b4f47ab071ac1ebe8534557896f72a4b78d01aad908e6ccf91402aa49e4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d6f7a65782f6c61726176656c2d746573742d6c616e65732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/mozex/laravel-test-lanes)

Two test runs started at the same time in one project corrupt each other's databases. Laravel numbers its parallel test databases by worker index, both runs count from 1, and every new worker drops and rebuilds the very tables the other run is using mid-test. This package ends that: each test process claims a machine-unique lane through a database advisory lock, so databases become `{base}_test_lane{n}` and never overlap. Two agents, two terminals, or you and an agent can run the suite in the same checkout at full parallel speed.

> **[Read the full documentation at mozex.dev](https://mozex.dev/docs/laravel-test-lanes/v1)**: searchable docs, version requirements, detailed changelog, and more.

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [The Problem](#the-problem)
- [How It Works](#how-it-works)
- [Serial Runs Are Covered Too](#serial-runs-are-covered-too)
- [Configuration](#configuration)
- [Cleaning Up Lane Databases](#cleaning-up-lane-databases)
- [Sizing the Connection Budget](#sizing-the-connection-budget)
- [What Is Not Isolated](#what-is-not-isolated)

Support This Project
--------------------

[](#support-this-project)

I maintain this package along with [several other open-source PHP packages](https://mozex.dev/docs) used by thousands of developers every day.

If my packages save you time or help your business, consider [**sponsoring my work on GitHub Sponsors**](https://github.com/sponsors/mozex). Your support lets me keep these packages updated, respond to issues quickly, and ship new features.

Business sponsors get logo placement in package READMEs. [**See sponsorship tiers →**](https://github.com/sponsors/mozex)

Installation
------------

[](#installation)

> **Requires [PHP 8.2+](https://php.net/releases/)** - see [all version requirements](https://mozex.dev/docs/laravel-test-lanes/v1/requirements)

Install it as a dev dependency:

```
composer require mozex/laravel-test-lanes --dev
```

That's the whole setup. The package wires itself when your tests run: its service provider registers the lane resolver during application boot, which always happens before Laravel's parallel-database machinery reads the token. Pest and PHPUnit alike, serial and `--parallel` alike.

Auto-registration triggers under `APP_ENV=testing`, Laravel's default in `phpunit.xml`. If your suite runs under a different environment name, register by hand in your base `TestCase` instead:

```
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Mozex\TestLanes\TestLanes;

abstract class TestCase extends BaseTestCase
{
    public function createApplication()
    {
        $app = parent::createApplication();

        TestLanes::register();

        return $app;
    }
}
```

`createApplication()` is the right spot for the manual form: `ParallelTesting` is a facade, so it needs a booted container, and this is the first point where one exists that still runs before the parallel-database callbacks. Registering earlier (in `Pest.php`, for example) throws "A facade root has not been set".

The Problem
-----------

[](#the-problem)

Laravel's parallel testing names each worker's database `{base}_test_{token}`, where the token is the paratest worker index. Those indexes always start at 1. Start a second run while the first is still going, in the same project, and both runs' worker 1 lands on `{base}_test_1`, both worker 2s on `{base}_test_2`, and so on.

The damage is not subtle. Each new worker process drops every table in its database and reloads the schema before testing. When that database is shared with a live run, the live run's tables vanish mid-test. Measured on a real project: the first of two simultaneous runs failed with 26 errors (`relation "users" does not exist`, inserts hitting half-loaded schemas), while the second run finished green on the freshly rebuilt tables. The failures always land on whoever started first.

Serial runs have it even worse: without `LARAVEL_PARALLEL_TESTING`, Laravel skips the database-switching machinery entirely, so every serial run on the machine shares the one base test database.

And no, paratest's `UNIQUE_TEST_TOKEN` does not save you. Laravel never reads it.

How It Works
------------

[](#how-it-works)

At the start of a run, each test process claims a lane: the lowest number in a pool (256 by default) whose advisory lock it can take on your database server. The lane becomes Laravel's parallel-testing token, so the process works in `{base}_test_lane{n}`. Laravel creates and migrates that database itself on first use, exactly as it does for its own parallel databases.

A lane claim is an advisory lock, not a lock file, and that choice carries the design:

- **Atomic.** Two processes can never hold the same lane, no matter how precisely they race. The second one just gets the next lane.
- **Self-healing.** The server drops the lock the moment the holding connection dies. A killed or crashed run frees its lane with no cleanup step and no stale state to reap.
- **Reusable.** Lanes are a small recycled pool rather than a unique id per run, so a lane's database keeps its migrated schema between runs. You pay the migration cost once per lane, not once per run.

The lock rides on a dedicated PDO connection that deliberately bypasses Laravel's connection manager, because Laravel is free to reconnect or purge its own handles mid-run.

Postgres, MySQL, and MariaDB are supported out of the box. Anything else fails loudly instead of silently sharing databases, and you can teach the package a new driver through the config.

There's a bonus: `Storage::fake()` also scopes its disk roots by the parallel token. With lanes registered, two concurrent runs stop deleting each other's fake-disk files too.

Serial Runs Are Covered Too
---------------------------

[](#serial-runs-are-covered-too)

`TestLanes::register()` forces `LARAVEL_PARALLEL_TESTING` on. That routes every run through the parallel-database machinery, including plain `php artisan test` or `vendor/bin/pest` without `--parallel`, so serial runs get their own lane database instead of sharing the base one. A serial run is just a run with one worker.

Configuration
-------------

[](#configuration)

Publish the config file if you want to change the defaults:

```
php artisan vendor:publish --tag=test-lanes-config
```

```
return [
    'enabled' => (bool) env('TEST_LANES_ENABLED', true),

    'pool_size' => (int) env('TEST_LANES_POOL_SIZE', 256),

    'locks' => [
        'pgsql' => PgsqlAdvisoryLock::class,
        'mysql' => MysqlAdvisoryLock::class,
        'mariadb' => MysqlAdvisoryLock::class,
    ],
];
```

Set `TEST_LANES_ENABLED=false` to switch the package off entirely; runs then behave exactly as they would without it. The pool costs nothing until processes actually claim lanes, so there's no reason to shrink it.

To support another database driver, implement the `Mozex\TestLanes\Locks\AdvisoryLock` interface (three methods: `connect`, `tryAcquire`, `release`) and map it in `locks`. That extends lane claiming; `test-lanes:cleanup` only knows how to list and drop databases on Postgres, MySQL, and MariaDB.

Cleaning Up Lane Databases
--------------------------

[](#cleaning-up-lane-databases)

Lane databases are kept on purpose: reusing a migrated database is the whole point. When reuse ends, because you're tearing down a checkout or renaming a database, drop them all at once:

```
php artisan test-lanes:cleanup
```

The command only drops databases whose lane lock is currently free. A lane claimed by a live test run is kept and reported, so running cleanup mid-test is safe. Pass `--connection=name` to clean a connection other than the default.

If you work on per-branch git worktrees through [mozex/laravel-worktree](https://github.com/mozex/laravel-worktree), there's nothing to run there: its `worktree:teardown` drops a worktree's lane databases on its own.

Sizing the Connection Budget
----------------------------

[](#sizing-the-connection-budget)

Each test worker holds two database connections: Laravel's own (to the lane database) and the lock holder (to the base database). A 24-worker run costs about 50 connections, and two of those at once will blow through a stock Postgres `max_connections = 100`.

That failure is not clean, either. Under connection starvation Laravel misreads "too many clients" as "the database is missing" and tries to drop it. Size the server before you need it:

```
max_connections >= 2 x (workers x concurrent runs) + headroom

```

For example: up to 3 concurrent 24-worker runs wants `max_connections` of at least 150, so 200 is a comfortable setting.

What Is Not Isolated
--------------------

[](#what-is-not-isolated)

The lane claim scopes **databases** (and `Storage::fake()` roots). It cannot reach things your suite keys on a fixed path or on paratest's raw `TEST_TOKEN`, such as a hardcoded port or a shared temp file. Those still collide between concurrent runs and need their own per-run scoping.

A few more things worth knowing:

- The base test database must exist; it's where the lock is taken. It's the same database your serial runs used before this package, so in practice it already does.
- The connection must use discrete `host`/`port`/`database` keys. A `DB_URL`-style connection is refused rather than guessed at.
- PgBouncer in transaction-pooling mode silently breaks session advisory locks. Point your test connection at Postgres directly.
- `test-lanes:cleanup` drops with `WITH (FORCE)` on Postgres, which needs PostgreSQL 13 or newer.
- On MySQL, cleanup can only see databases the connecting user has privileges on. A locked-down user makes lane databases invisible to the command, and it reports nothing to drop.
- Laravel's `--recreate-databases` and `--drop-databases` flags act on the worker-index databases, not on lanes: the parallel runner's process hooks re-register the token resolver with the raw worker index. Drop lane databases with `test-lanes:cleanup` instead.
- The same worker-index mismatch leaves compiled Blade views behind under `--parallel`: the runner cleans view directories named for the raw worker index while tests compile into lane-named ones. Nothing breaks, but the lane view directories linger and grow quietly until something clears them.

Resources
---------

[](#resources)

Visit the [documentation site](https://mozex.dev/docs/laravel-test-lanes/v1) for searchable docs auto-updated from this repository.

- **[AI Integration](https://mozex.dev/docs/laravel-test-lanes/v1/ai-integration)**: Use this package with AI coding assistants via Context7 and Laravel Boost
- **[Requirements](https://mozex.dev/docs/laravel-test-lanes/v1/requirements)**: PHP, Laravel, and dependency versions
- **[Changelog](https://mozex.dev/docs/laravel-test-lanes/v1/changelog)**: Release history with linked pull requests and diffs
- **[Contributing](https://mozex.dev/docs/laravel-test-lanes/v1/contributing)**: Development setup, code quality, and PR guidelines
- **[Questions &amp; Issues](https://mozex.dev/docs/laravel-test-lanes/v1/questions-and-issues)**: Bug reports, feature requests, and help
- **[Security](mailto:hello@mozex.dev)**: Report vulnerabilities directly via email

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance100

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 87.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4d33a9cb8263eab6fa251e57fc763c5c6977701e5cafa0edc538c4651bb9780?d=identicon)[mozex](/maintainers/mozex)

---

Top Contributors

[![mozex](https://avatars.githubusercontent.com/u/18025667?v=4)](https://github.com/mozex "mozex (21 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (3 commits)")

---

Tags

advisory-locksdatabasedeveloper-toolslaravelparallel-testingparatestpestphpphpunittest-isolationtestingtestingphpunitpestlaraveldatabaseisolationparallelmozexparatest

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mozex-laravel-test-lanes/health.svg)

```
[![Health](https://phpackages.com/badges/mozex-laravel-test-lanes/health.svg)](https://phpackages.com/packages/mozex-laravel-test-lanes)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M275](/packages/laravel-ai)[spatie/laravel-health

Monitor the health of a Laravel application

88212.7M180](/packages/spatie-laravel-health)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[spatie/laravel-medialibrary

Associate files with Eloquent models

6.2k45.4M679](/packages/spatie-laravel-medialibrary)[illuminate/queue

The Illuminate Queue package.

20433.0M1.7k](/packages/illuminate-queue)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9872.4M142](/packages/roots-acorn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
