Released on June 5, 2026, PHPUnit 13.2 represents a decisive moment in the evolution of the PHP ecosystem. While previous iterations focused on transitioning away from older PHP versions, this update tackles a fundamental architectural habit that has long plagued test suites: the over-reliance on ambiguous test doubles. By deprecating the any() matcher, Sebastian Bergmann and the PHPUnit team are steering the community toward a future where "explicit" is always better than "implicit."
This update isn't just a minor version bump; it’s a philosophical mandate. It forces developers to stop using mocks as a "one-size-fits-all" solution and start distinguishing between the data they provide to a system and the behavior they expect from it. For projects running on PHP 8.4 and beyond, this release offers significant performance and clarity benefits, provided you are willing to refactor away from the "easy" path of generic invocation matchers.
H2: The End of Ambiguity: Deprecating any() in PHPUnit 13.2
The any() matcher has historically been the "easy button" of PHPUnit. Whether you needed a simple value return or a complex interaction, any() allowed developers to bypass the cognitive load of deciding how many times a method should be called. However, this convenience came at the cost of clarity. PHPUnit 13.2 officially deprecates any() in favor of dedicated stubbing methods, highlighting a fundamental problem: any() blurs the line between state-based testing and behavior-based testing.
When we use any(), we often fail to document the intent of the test. Are we just trying to get the code to run by providing data (Stubbing), or are we verifying that a specific interaction occurs (Mocking)? By removing this ambiguity, PHPUnit 13.2 ensures that the test double serves a single, clear purpose. In this version, trying to use any() will trigger deprecation notices, signaling that the engine will soon require you to define your test doubles with more precision.
This shift prioritizes readability. Future maintainers won't have to guess if the number of method calls matters; the code will explicitly state it through methods like willReturn() on a stub or expects($this->once()) on a mock. For legacy codebases, this means the honeymoon period of upgrading from PHPUnit 12.x is over; the warnings are immediate and vocal, demanding a refactor of generic mock configurations.
H2: Architecture and Performance Gains for PHP 8.4+
PHPUnit 13.2 is finely tuned for the PHP 8.4 engine. By leveraging new engine optimizations—specifically around how internal proxies and reflection-based classes are handled—the framework has significantly reduced the overhead of test double creation.
One of the most notable improvements is the Reduced Memory Overhead. Traditional mock objects that track every invocation (as required by matchers like any()) carry a heavy state object. By moving toward explicit stubs that do not track invocation counts, PHPUnit 13.2 can allocate much leaner objects. In high-concurrency CI/CD pipelines, this reduction in memory footprint can lead to noticeably faster test execution times, especially in suites with thousands of dependencies.
Furthermore, PHPUnit 13.2 utilizes PHP 8.4’s enhanced type safety to catch invalid configurations earlier. For example:
// PHPUnit 13.2 leverages PHP 8.4 property hooks and stricter types
$stub = $this->createStub(UserRepository::class);
$stub->method('find')->willReturn(new User(id: 1)); // Type-checked at the engine level
This version utilizes modern PHP syntax to generate proxy objects that are more compatible with static analysis tools. By generating doubles that mirror the stricter type requirements of PHP 8.4, PHPUnit avoids the runtime "type-juggling" errors that often occurred when mocks interacted with union types or DNF (Disjunctive Normal Form) types.
H2: Redefining Testing Standards: Mocks vs. Stubs
For years, the industry standard was to use $this->createMock() for almost everything. PHPUnit 13.2 aggressively pushes back against this "one-size-fits-all" approach. The framework now encourages a strict "Double Distinction."
The core philosophy is simple: Verify behavior only when necessary.
- Stubs: Use these for data injection. If your test needs a database result to continue, use
createStub(). It is faster and carries no behavioral expectations. - Mocks: Use these for side effects. If your test must ensure an email was sent or a log was written, use
createMock()with explicit expectations (once(),exactly(n)).
This distinction leads to Cleaner Failure Messages. Because a stub isn't "expecting" anything, PHPUnit can provide a specific error if you accidentally try to verify an invocation on it. Instead of a generic "Expectation failed" message, you get "Unexpected method call on Stub," which immediately tells the developer they are using the wrong tool for the job.
Most importantly, this reduces Brittle Tests. Over-specifying tests by using any() or unnecessary mocks often leads to failures during refactoring, even when the logic remains correct. By using explicit stubs, you decouple your tests from the internal implementation details of how a result is fetched, focusing instead on the result itself.
H2: Migration Strategy and Future-Proofing
Transitioning to PHPUnit 13.2 requires a tactical approach. You cannot simply ignore the any() deprecations, as they serve as the foundation for the upcoming PHPUnit 14.
The primary refactoring path involves replacing generic mocks with specialized stubs:
// Old Way (Ambiguous)
$mock = $this->createMock(Service::class);
$mock->expects($this->any())->method('getData')->willReturn('foo');
// New Way (Explicit Stub)
$stub = $this->createStub(Service::class);
$stub->method('getData')->willReturn('foo');
To handle large-scale migrations, the community is already rallying around tools like Rector. Automated rulesets for PHPUnit 13.2 can identify any() matchers that don't have accompanying assertions and automatically convert those mocks into stubs. This automation is critical for reducing technical debt without requiring hundreds of manual hours.
The long-term maintenance benefits are clear. By enforcing these constraints now, PHPUnit 13.2 lowers the barrier for new developers. When a test uses createStub(), the developer knows immediately that they don't need to worry about call counts. This clarity is a direct investment in the project's longevity. As we look toward PHPUnit 14, it is evident that the framework is moving toward a highly optimized, specialized architecture where the "generic mock" may become an anti-pattern.
In conclusion, PHPUnit 13.2 is a necessary "tough love" update for the PHP community. By deprecating the comfort of any() and leaning into the performance capabilities of PHP 8.4, it forces us to write better, faster, and more intentional tests. Embracing these stricter standards today is the only way to ensure our test suites remain assets rather than liabilities in the years to come.