I originally sent this article to my subscribers as a newsletter on April 3, 2026. See the end of this article for details.
Data providers meet closures
Data providers let you run the same test method with multiple input sets without duplicating the test method. Until now, connecting a data provider to a test method required referencing the provider method by its name as a string. This works, but some developers prefer to provide the test data directly at the test method: using explicit syntax rather than a string reference to a separate method.
PHP 8.5 introduced support for closures in constant expressions. PHPUnit 13.1 takes advantage of this with the new #[DataProviderClosure] attribute, as implemented in pull request #6526. Instead of referencing a separate method by name, you can now define the data provider directly as a static closure:
#[DataProviderClosure( static function (): array { return [ 'zeros' => [0, 0, 0], 'one plus one' => [1, 1, 2], ]; } )] public function testAdd(int $a, int $b, int $expected): void { $this->assertSame($expected, $a + $b); }
The closure must be static and return an array or Traversable that provides the argument sets for the test method, just like a conventional data provider method.
Please note that #[DataProviderClosure] requires PHP 8.5 or later, because closures in constant expressions are not supported in earlier PHP versions.
Richer Open Test Reporting
Open Test Reporting (OTR) is a vendor-neutral format for recording and sharing test results. PHPUnit has supported writing OTR XML logfiles for some time now, and with PHPUnit 13.1 the information captured in these logfiles becomes significantly more comprehensive.
Unexpected output
When a test produces output that was not expected, for example through print or var_dump() calls, this output is now included in the OTR XML logfile (#6501). Previously, this information was only visible in the console. By capturing it in the logfile, tools that process OTR data can now show unexpected output alongside other test results, making it easier to identify and clean up accidental debug output in your test suite.
Issues
Deprecations, notices, warnings, and errors that occur during test execution are now reported in the OTR XML logfile (#6524). Each issue is recorded with structured metadata including its type, message, source location, and — for deprecations — whether the trigger was direct, indirect, or originated from first-party code.
Tools that consume OTR data now see not just pass/fail results but also the issues that occurred during the run.
Group information
Test groups assigned through the #[Group] attribute are now included in the OTR XML logfile as tags (#6523). This enables tools built on top of OTR to filter, categorise, and present test results based on the same grouping that you already use to organise your test suite.
Custom issue trigger resolvers
PHPUnit classifies deprecations based on where they are triggered and where they originate. This classification determines, for example, whether a deprecation is reported as “direct” or “indirect”, which in turn affects how it is displayed and whether it causes the test suite to fail.
In most cases, PHPUnit's built-in logic handles this classification correctly. However, there are situations where frameworks or libraries sit between your code and the actual source of a deprecation, causing misclassification. A common example is Symfony's DebugClassLoader, which triggers deprecations on behalf of other classes. PHPUnit would attribute such a deprecation to the class loader rather than to the code that actually caused it.
PHPUnit 13.1 addresses this with support for custom issue trigger resolvers (#6530). You can now implement your own resolver and register it in your XML configuration file using the new <issueTriggerResolvers> element:
<source> <include> <directory>src</directory> </include> <issueTriggerResolvers> <class name="App\Testing\SymfonyIssueTriggerResolver"/> </issueTriggerResolvers> </source>
A custom resolver examines the stack trace and the deprecation message, and can provide corrected caller and callee information when it recognises the pattern. If a resolver does not handle a particular case, PHPUnit falls back to the next resolver in the chain, with the built-in default resolver as the final fallback.
This means that frameworks like Symfony can ship a resolver that correctly attributes deprecations triggered by their infrastructure code, ensuring that the classification you see in your test results accurately reflects where the issue actually originates.
Code Coverage and PHPCOV
PHPUnit 13.1 ships with phpunit/php-code-coverage 14.0, which brings significant improvements to how code coverage data is serialised and how reports are generated. Alongside this, PHPCOV 13.0 has been released with support for merging code coverage data across different machines and build environments.
These changes are large enough to deserve a separate article, which I will publish soon. For now, here is a brief overview of the most notable improvements:
- Serialised code coverage data now uses relative paths, making it possible to collect coverage on one machine and generate reports on another.
- PHPCOV 13.0 can merge serialised code coverage files even when they were generated with different base paths, which is essential for distributed CI pipelines.
- The HTML code coverage report uses a new, more colour-blind-friendly palette by default, and the colours can be customised through PHPUnit's XML configuration file.
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.