brianium/paratest
ParaTest brings zero-configuration parallel testing to PHPUnit. Run tests across multiple processes by TestCase or Test, and combine code coverage into one report. Install via Composer and use vendor/bin/paratest with no extra bootstrap needed.
ParaTest is the parallel test runner that Laravel’s own php artisan test --parallel command is built on. If your Laravel suite spends most of its time in feature tests that boot the framework and hit the database, this is the first lever to pull.
Start here:
Check whether ParaTest is already installed:
composer show brianium/paratest
If not, install it as a dev dependency:
composer require --dev brianium/paratest
Before committing to a version, check compatibility with your pinned phpunit/phpunit and Symfony versions. ParaTest intentionally tracks only the latest PHPUnit, so Laravel 9/10 projects may need an older ParaTest major or a pinned minor. Use composer why-not brianium/paratest and the release notes when resolving conflicts.
Try Laravel’s wrapper first:
php artisan test --parallel
php artisan test --parallel --processes=4
Laravel will create per-process test databases and set TEST_TOKEN for each worker. That is usually enough for a standard Laravel app.
If you need direct control, use the binary:
vendor/bin/paratest --help
vendor/bin/paratest --processes=4
First use case: take a slow MySQL-backed feature suite and run php artisan test --parallel --processes=4 --recreate-databases. If it works, benchmark with 4 vs 8 processes. If tests collide over the database, move to token-aware configuration described below.
Where to look first in the package docs: Test token, Initial setup for all tests, Troubleshooting, and Caveats in the README. Also run vendor/bin/paratest --help; ParaTest forwards many PHPUnit options, but not all, and coverage/reporting options have been added incrementally.
Use Laravel’s parallel runner as the default path. For most Laravel teams, php artisan test --parallel is the day-to-day command. It handles database creation, migrations, and token injection. Useful options:
php artisan test --parallel --processes=8
php artisan test --parallel --recreate-databases
php artisan test --parallel --coverage --min=80
Customize per-process database setup with ParallelTesting. Laravel exposes hooks in tests/TestCase.php:
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\ParallelTesting;
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
parent::setUp();
ParallelTesting::setUpTestDatabase(function (string $database, int $token) {
// Seed shared reference data, configure extensions, etc.
});
ParallelTesting::setUpProcess(function (int $token) {
// Per-process setup: cache prefix, storage path, etc.
});
}
}
Use this for shared seed data or per-process external resource namespacing. Do not use it for logic that must happen exactly once for the whole run; it runs per process.
Configure token-aware connections when calling ParaTest directly. If you bypass php artisan test --parallel, each worker needs its own database. Use TEST_TOKEN:
// config/database.php
'testing' => [
'driver' => 'mysql',
'database' => 'testdb_'.(getenv('TEST_TOKEN') ?: '0'),
// ...
],
For SQLite file databases, use a unique file per process:
'database' => database_path('testing_'.(getenv('TEST_TOKEN') ?: '0').'.sqlite'),
Or keep using :memory: SQLite, which is naturally isolated per process. For MySQL/Postgres, create the token databases before the run, or use Laravel’s parallel runner, which does it for you.
Namespace external services per process. Tests that share Redis, file cache, queues, or object storage will interfere under parallel execution. In phpunit.xml prefer process-local drivers:
<env name="CACHE_STORE" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
If you must use Redis, prefix keys with TEST_TOKEN or, better, UNIQUE_TEST_TOKEN:
'prefix' => 'laravel_'.(getenv('UNIQUE_TEST_TOKEN') ?: 'local'),
TEST_TOKEN is distinct per running process; UNIQUE_TEST_TOKEN is unique per run and process. Use the latter for external resources that persist across CI runs.
Coverage merging is a strong reason to use ParaTest. With Xdebug:
XDEBUG_MODE=coverage php artisan test --parallel --coverage --min=80
With PCOV, enable it in child processes via --passthru-php:
php -d pcov.enabled=1 vendor/bin/paratest \
--passthru-php="'-d' 'pcov.enabled=1'" \
--coverage-clover build/coverage.xml
ParaTest combines coverage from workers into one report. Recent versions also support --coverage-openclover, --coverage-text, and --only-summary-for-coverage-text.
Use --functional selectively. By default ParaTest parallelizes by test class/file. --functional parallelizes by test method, which helps when a handful of large test classes dominate the suite. The trade-off is more process overhead and weaker support for @depends and class-level state. Prefer the default mode for normal Laravel feature suites.
Shard in CI when one machine is not enough. Recent ParaTest versions support --shard:
vendor/bin/paratest --shard=1/4 --processes=4 --log-junit junit-1.xml
Combine shard reports in CI. This is useful for large suites where even 8–16 local processes are not enough.
PHPStorm integration. Use the dedicated helper binary in the PHPUnit run configuration’s interpreter options:
./vendor/bin/paratest_for_phpstorm
Add ParaTest options under “Test runner options”. Ensure PHPUnit and coverage are already configured in PHPStorm; the helper relies on that setup.
Pest projects. Pest’s --parallel support also uses ParaTest:
./vendor/bin/pest --parallel
The same TEST_TOKEN, database, and external-service isolation rules apply.
Debug failing workers. Run with --verbose. ParaTest prints the originating worker command when a subprocess fails. Copy that command, remove the --printer ...NullPhpunitPrinter option, and rerun it to see PHPUnit’s normal output. This is the fastest way to debug worker-only failures.
composer why-not, pin a compatible minor, and avoid composer update brianium/paratest in isolation. Laravel 9 projects may need ParaTest v6 rather than v7.private static bool $initialized flag runs once per worker. If you need exactly-once initialization for the entire invocation, use a filesystem lock (flock) as shown in the README, or move the work into a CI/pre-test step. Laravel’s ParallelTesting::setUpProcess is per process, not per run.RefreshDatabase/DatabaseTransactions against one shared MySQL database will deadlock or leak state across workers. Use Laravel’s parallel database creation, token-suffixed databases, or :memory: SQLite. File-based SQLite shared across processes is a common source of “database is locked” errors.TEST_TOKEN is not a run identifier. It only guarantees uniqueness among currently running processes. Use UNIQUE_TEST_TOKEN for external namespaces, S3 buckets, Redis prefixes, and anything that could collide with a previous CI run.--functional breaks more than it fixes in some suites. It runs each test method in a separate process, so @depends, setUpBeforeClass, and class-level assumptions may not behave as expected. Also check your ParaTest version for JUnit merging fixes in functional mode (fixed in 7.11.1).XDEBUG_MODE=coverage in the environment. PCOV often needs --passthru-php so child PHP processes enable it. Quoting for --passthru-php is shell-sensitive; verify locally and in CI.--verbose and rerun the printed worker command without the null printer when debugging.--processes explicitly. More processes can slow you down when each Laravel worker boots, hits the database, and consumes memory.vendor/bin/paratest --help for the supported set. Coverage and TestDox options have been added over time (--testdox-text/--testdox-html in 7.14.0, time-limit options in 7.10.0), so older pinned versions may silently lack them.php artisan test --parallel exposes --processes, --recreate-databases, and coverage/minimum options, but if you need sharding or specific ParaTest flags, call vendor/bin/paratest directly.How can I help you explore Laravel packages today?