diablomedia/phpunit-pretty-printer
A drop-in PHPUnit result printer that swaps the default console output for colorful, readable test results. See test progress, failures, exception diffs, and timing at a glance, with configurable verbosity for CI and local runs.
Install as a dev dependency so it never ships to production:
composer require --dev diablomedia/phpunit-pretty-printer
Verify the FQCN before wiring it up — the repo is unlisted, so confirm the class name from the vendor tree:
composer show diablomedia/phpunit-pretty-printer
grep -r "class .*Printer" vendor/diablomedia/phpunit-pretty-printer/src
Then register it as the run's result printer either per-run (safest first step, zero config changes):
vendor/bin/phpunit --printer 'DiabloMedia\PHPUnit\PrettyPrinter\PrettyPrinter'
…or persistently in your PHPUnit config. For PHPUnit 8/9, the printer class lives on the root <phpunit> element:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit printerClass="DiabloMedia\PHPUnit\PrettyPrinter\PrettyPrinter"
colors="true"
bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
</testsuites>
</phpunit>
Look here first: phpunit.xml.dist (the committed default) and any phpunit.xml override. Keep the printer in the local phpunit.xml (gitignored) rather than .dist, so CI keeps the standard, machine-parseable output.
First use case: you have a suite where a handful of test files dominate wall-clock time but the default dot-progress output hides which ones. Run the suite with this printer, watch the per-file lines stream by with their timings, and immediately see which files are the hot spots — the natural next step is splitting those into a separate suite or parallelising them with paratest.
Local-dev default, CI-standard output. Put the printer in phpunit.xml and leave phpunit.xml.dist untouched. PHPUnit merges phpunit.xml over .dist, so developers get pretty per-file progress while CI (which only checks out .dist) gets the vanilla output its parsers expect.
Composer script shortcut. Wrap the invocation so nobody has to remember the FQCN:
{
"scripts": {
"test": "phpunit",
"test:pretty": "phpunit --printer 'DiabloMedia\\PHPUnit\\PrettyPrinter\\PrettyPrinter'",
"test:ci": "phpunit --log-junit build/junit.xml --coverage-clover build/clover.xml"
}
}
composer test:pretty becomes the everyday command; composer test:ci is what the pipeline calls.
Targeted runs while iterating. The printer pairs well with narrowing flags — the per-file header plus timing is most useful when you're running a slice of the suite:
vendor/bin/phpunit --printer '...' --testsuite unit
vendor/bin/phpunit --printer '...' tests/Feature/Checkout
vendor/bin/phpunit --printer '...' --filter 'test_authorize'
Timing-driven suite surgery. Use the output as an investigative tool, not just cosmetics:
composer test:pretty 2>&1 | tee /tmp/profile.txt@group slow and exclude by default, or fan out with paratest.Pair it with a logger for data you can actually diff. The printer is for eyeballs; JUnit XML is for trend tracking. Run both in one invocation by using --printer on the CLI plus <logging> in config — loggers are independent of the result printer.
Team onboarding / demo pattern. Because it's a printer swap and not a test-listener, it has no effect on pass/fail. That makes it a low-risk thing to add to a shared dev Docker image or a Makefile target, and to drop in during pairing sessions when someone asks "why is the suite slow?"
It's a printerClass — API churn is the real risk. The last release was December 2021, squarely in the PHPUnit 8/9 era. PHPUnit 10 reworked the result-collector/event system and removed the XML printerClass attribute along with the older printer contract. Pin your PHPUnit major and confirm the printer loads before standardising on it — a silent fallback to the default printer with a deprecation notice is the most likely failure mode. If you're on PHPUnit 9 already, this is a comfortable fit; on 10+ treat it as unsupported until proven otherwise.
Keep it out of phpunit.xml.dist. Beyond CI parsing, printerClass in a committed config becomes a hard dependency for every contributor and every CI cache that doesn't run composer install with dev deps (--no-dev installs will fail to find the class and abort the run).
Any stray output corrupts the display. The printer draws streaming per-file lines; echo, var_dump, fwrite(STDERR, ...), and un-suppressed deprecations/notices from your own bootstrap will interleave and shred the layout. If the output looks scrambled, hunt for output in setUp, custom assertions, or a bootstrap that prints.
--debug and --testdox fight it. --debug swaps in its own printer, and --testdox is a separate formatter — don't expect them to compose. Pick one per run.
Colour and TTY assumptions. ANSI colour codes are emitted for terminals. On CI or when piping to a file, either rely on colors="false" in config or accept the escape sequences and strip them when post-processing (sed -r 's/\x1B\[[0-9;]*[mK]//g'). Non-UTF8 locales and very narrow terminals can wrap the per-file lines awkwardly.
Only one result printer at a time. You cannot have this and another printerClass simultaneously. If a teammate needs TeamCity output, that's a separate invocation — use <logging>/--log-junit for the machine-readable artefacts that can coexist.
Expect a small overhead on huge suites. Printing a line per file is cheap, but on thousands of files in a hot loop the I/O is non-zero. If you're micro-benchmarking, benchmark with the default printer and reserve this for profiling runs.
Extension points are thin by design. This is an output formatter, not a listener — it doesn't give you hooks to mutate tests. If you need per-test timing that feeds a custom report or a slow-test budget gate, don't try to bend the printer: write a proper PHPUnit extension/TestListener (or read the JUnit XML the printer run also produced) and let the printer stay a human-facing view.
Version-lock discipline. With a single 2021 release and 77 stars, treat it as unmaintained-but-stable: commit composer.lock, and note in your README/ADR why PHPUnit is pinned. If a future PHPUnit upgrade blocks you, the escape hatch is trivial — the data you actually want (per-file time) is in JUnit XML, so a small script over --log-junit output reproduces 90% of the value.
How can I help you explore Laravel packages today?