I originally sent this article to my subscribers as a newsletter on June 5, 2026. See the end of this article for details.
PHPUnit 13.2 is a release about precision and clarity. It gives you finer control over which tests run and in what order, makes the feedback you get when a test fails both faster and easier to read, and adds more expressive ways to describe how your test doubles should behave.
Running exactly the tests you want
PHPUnit has always let you narrow a run down to a subset of your test suite, for example by test suite, by group, or with the --filter option and its regular expressions. Regular expressions are powerful, but they are also easy to get wrong: a pattern can match more tests than you intended, or fail to match at all because of a single unescaped character.
PHPUnit 13.2 adds a more precise mechanism built around test IDs. A test ID is a stable identifier that names a single test unambiguously, including the specific data set for tests that are driven by a data provider. The new --list-test-ids option (#6575) prints every test in your suite as a test ID, and --run-test-id (#6577) runs exactly the one test that the ID you pass refers to, using exact matching rather than a pattern.
Building on this, you can now hand PHPUnit an entire list of tests to run (#3387): the --test-id-filter-file option reads a file that contains one test ID per line and runs precisely those tests. This is particularly useful for tooling and continuous integration. A previous run can record the IDs of the tests that failed so that a later run re-executes only those, or a build can shard a large suite by handing each worker its own list of IDs. The --filter option has also been extended to understand test ID syntax (#6575), so you can keep using the option you already know while gaining the precision of test IDs.
A related change makes filtering both faster and more correct (#6609). When the test that a data provider supplies cannot possibly match the active --filter pattern, PHPUnit now skips executing that data provider entirely instead of running it only to discard its result.
The order tests run in
Once you have decided which tests to run, PHPUnit 13.2 gives you more control over the order in which they run. The test runner can already order tests by size, by duration, by their declared dependencies, by putting previously failing tests first, or randomly.
Duration-based ordering gains a descending variant (#6075), so you can run the slowest tests first instead of last. Now that both directions exist, the old direction-agnostic values have become ambiguous. The --order-by duration and --order-by size options, along with the corresponding executionOrder="duration" and executionOrder="size" values in the XML configuration file, are therefore deprecated in favour of the explicit duration-ascending, duration-descending, size-ascending, and size-descending variants.
The “defects first” ordering has been refined as well. Previously it re-prioritised not only the tests that produced errors or failures, but also those that triggered deprecations, notices, or warnings, as well as incomplete, risky, and skipped tests. From PHPUnit 13.2 on, only errors and failures are treated as defects for the purpose of reordering. This makes the behaviour match what most people expect: the tests that actually broke run first.
Finally, the executionOrder attribute in the XML configuration file now accepts richer combinations. You can combine defects with any main order, and even express three-way combinations of dependency resolution, defect prioritisation, and a main order, such as depends,defects,duration-ascending. A sorting bug that mishandled test suites built from TestCase classes has also been fixed (#6582).
Faster, clearer failures
When a test fails, two things matter: how quickly PHPUnit can tell you what went wrong, and how easy that explanation is to read. PHPUnit 13.2 improves both.
The biggest speed improvement comes from a dependency upgrade. PHPUnit 13.2 ships with version 9 of sebastian/diff, the library that produces the diffs you see when a comparison fails. Its diff engine has been replaced with an implementation of Eugene W. Myers' linear-space algorithm (pull request #138). For large inputs that are mostly identical, the difference is dramatic: on a benchmark comparing two files of around 18,000 lines that differ in roughly 200 lines, generating the diff dropped from tens of seconds to a few milliseconds. If you have ever watched PHPUnit appear to hang while building the diff for a failed assertion on a large string or array, this is the change that fixes it.
You can now also control how much context a diff shows. The new --diff-context option (#6567) sets the number of unchanged lines displayed around each change, which defaults to three. Increase it when you need more of the surrounding picture, or reduce it to keep the output tight.
Some failure messages have become less noisy. The IsTrue, IsFalse, IsNull, IsFinite, IsInfinite, and IsNan constraints no longer dump the entire array or object that was passed to them (#5810). For a check that only asks whether a value is, say, true or null, exporting the full value added clutter without adding information.
For runs that produce a lot of output, the new --compact option, which can also be enabled through the PHPUNIT_COMPACT_OUTPUT=1 environment variable, switches to a more condensed console output (#6597).
PHPUnit 13.2 also adds assertions that compare strings while ignoring differences in whitespace (#5757), including variants that read the expected or actual value from a file. These are handy when comparing generated output such as source code or HTML, where indentation and line breaks are irrelevant to correctness.
A more dependable test runner
A test runner has to share the PHP runtime with the code it is testing, and several changes in PHPUnit 13.2 make it a better-behaved guest.
The most significant of these concerns error handlers. If your application registered an error handler before PHPUnit started, for example through an auto_prepend_file directive or a bootstrap script, PHPUnit used to silently replace it. That not only disabled behaviour you may have relied on, it could also produce false “risky test” warnings (#5845). PHPUnit 13.2 now chains the previously registered handler instead of disabling it (#5873), so both PHPUnit and your own error handling can do their jobs.
Tests that manipulate PHP's output buffering could previously confuse PHPUnit's own output capture, leading to incorrectly captured output, hangs, and even silent failures. This has been fixed (#5851). Issues such as deprecations, notices, and warnings that are triggered outside the scope of a test, for instance during bootstrapping, are now handled properly rather than being misattributed or lost (#6579).
Interrupting a run is friendlier too. When you stop a run, for example with Ctrl-C, PHPUnit now handles the interrupt gracefully and shows you the results gathered so far instead of simply terminating (#4201).
Two further changes help you catch mistakes in how you invoke PHPUnit. The new --validate-configuration option checks your XML configuration file for problems, and PHPUnit now emits a warning when you pass conflicting command-line options instead of quietly honouring one and ignoring the other (#6346).
More expressive test doubles
PHPUnit 13.2 rounds out the family of expectations that replaced the removed withConsecutive() method. In an earlier article I described withParameterSetsInOrder(), which expects parameter sets in an exact sequence, and withParameterSetsInAnyOrder(), which expects them in any order. Real interactions often fall between these two extremes, and the new withParameterSetsInPartialOrder() rule (#6606) covers that middle ground: you can pin some parameter sets to fixed positions while leaving the rest unordered.
Stubbing return values has become more precise as well (#6574). The willReturnMap() method, which maps sets of arguments to the values that should be returned for them, now accepts constraints in addition to literal values, so a mapping can match arguments by a rule rather than only by exact value. A new willReturnStrictMap() method uses strict comparison when matching arguments, avoiding the surprises that loose comparison can cause.
Finally, expressing expectations about exception messages is clearer. The long-standing expectExceptionMessage() method was misleadingly named: despite the name, it never required the message to be equal to the expected string, only to contain it. PHPUnit 13.2 introduces an explicit API (#6559):
-
expectExceptionMessageIs()requires an exact match -
expectExceptionMessageIsOrContains()keeps the familiar substring behaviour -
expectExceptionMessageMatches()remains available for regular expressions
Tests that use expectExceptionMessage(), which is now deprecated, should be migrated to one of the above.
Exclusive insights delivered to your inbox
Every two months, alongside each PHPUnit feature release, I send subscribers a detailed walkthrough of the new features: what they do, how they are implemented, and why they were added. It is the context that the ChangeLog does not cover.
I publish the content of the newsletter here on this website a month after my subscribers have received it. Subscribe now to get it as early as possible.