Running Laravel's test suite in parallel speeds things up considerably, but it also makes it easy to miss PHPUnit notices. The parallel worker output gets interleaved and buffered, and notices about deprecated API usage or risky tests tend to scroll past unnoticed — or disappear entirely. This post shows the command I use to surface them reliably.
The command
LARAVEL_PARALLEL_TESTING=1 \
LARAVEL_PARALLEL_TESTING_RECREATE_DATABASES=1 \
vendor/brianium/paratest/bin/paratest \
--colors=always \
--configuration=/path/to/phpunit.xml \
--runner=\\Illuminate\\Testing\\ParallelRunner \
--display-phpunit-notices \
--fail-on-all-issues \
tests/Unit
What each part does
Environment variables
LARAVEL_PARALLEL_TESTING=1 activates Laravel's parallel testing support. It causes the framework to spin up separate database connections per worker (suffixed _1, _2, etc.) and seed each one independently.
LARAVEL_PARALLEL_TESTING_RECREATE_DATABASES=1 forces those databases to be dropped and recreated from scratch on every run. Without it, a previous run's leftover state can cause tests to pass or fail for the wrong reasons — particularly relevant when you change a migration between runs.
Invoking paratest directly
Laravel's php artisan test --parallel is a thin wrapper around brianium/paratest. Calling the binary directly gives you access to flags that the Artisan wrapper doesn't expose, particularly the notice-related ones below.
--runner=\\Illuminate\\Testing\\ParallelRunner tells paratest to use Laravel's own runner class, which handles the database token injection and other framework-specific setup that the default runner skips.
--configuration takes an absolute path to your phpunit.xml. When you invoke paratest from outside the project root — for example, from a CI script or a Makefile — relative paths silently resolve to the wrong location. Using an absolute path avoids that class of silent misconfiguration.
Surfacing notices
--display-phpunit-notices is the key flag. PHPUnit emits notices for things like:
- calls to deprecated assertion methods (
assertContainson a string instead ofassertStringContainsString) - tests marked
@coversthat cover no code - tests with no assertions when
beStrictAboutTestsThatDoNotTestAnythingis enabled
In a parallel run these notices are buffered per worker and often never reach the terminal. This flag ensures they are printed to output regardless.
--fail-on-all-issues treats any notice, warning, or deprecation as a test suite failure. This is what makes the command useful for CI: the exit code becomes non-zero the moment any worker emits a notice, so the pipeline fails and forces you to deal with it rather than letting it accumulate.
Scoping to tests/Unit
Passing tests/Unit as the path restricts the run to unit tests, which tends to surface notices faster than a full suite run because unit tests don't need a running server or real database queries. Once you've cleared the unit test notices, you can repeat with tests/Feature or omit the path entirely.
Reading the output
When a notice fires, paratest prints it alongside the failing worker output. The format looks like:
NOTICE tests/Unit/Services/OrderServiceTest.php:42
Method Illuminate\Testing\Assert::assertContains() is deprecated. Use assertStringContainsString() instead.
The file path and line number point directly to the test method that triggered it. If you see the same notice repeating across many tests, the issue is usually in a shared base class or a trait — check the stack trace for the actual call site rather than fixing every test individually.
Making it a habit
The goal is to run with --fail-on-all-issues in CI from the start, before notices accumulate. If you're adding this to an existing codebase, it's usually easier to tackle notices test-file by test-file: run against a single file first, fix what you find, then broaden the path incrementally.
# Start with one file
LARAVEL_PARALLEL_TESTING=1 \
vendor/brianium/paratest/bin/paratest \
--runner=\\Illuminate\\Testing\\ParallelRunner \
--display-phpunit-notices \
--fail-on-all-issues \
tests/Unit/Services/OrderServiceTest.php
Once the file is clean, commit and move on to the next. The --fail-on-all-issues flag acts as a ratchet: once a file is notice-free, CI will catch any regression immediately.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.