Skip to content
Programing

Precision over Convenience: The Era of Explicit Testing in PHPUnit 13.2

Published: Duration: 6:02
0:00 0:00

Transcript

Host: Hey everyone, welcome back to Allur, the show where we break down the latest shifts in the tech landscape, from Laravel internals to the cutting edge of Go. I’m your host, Alex Chan. Host: Joining me today is Marcus Thorne. Marcus is a lead engineer who’s spent the last decade maintaining massive enterprise test suites and has been an early tester for the PHPUnit 13.x branch. Marcus, it is so great to have you on Allur. Guest: Thanks, Alex! It’s great to be here. Although, I have to say, talking about deprecating `any()` feels a bit like telling people their favorite comfortable old sneakers are being thrown away. People have some... *feelings* about this one. Host: Oh, I bet! I’ll admit, when I saw the release notes, I felt a little pang of guilt. I’ve definitely used `any()` as a "get out of jail free" card when I just wanted a test to pass. So, let’s dive right in. Why is this happening now? Why is the PHPUnit team basically forcing us to be more specific? Guest: It’s really about the evolution of the language. For the longest time, we used `$this->createMock()` for everything. It was the one-size-fits-all solution. But the problem is that `any()` is incredibly ambiguous. When you see `$mock->expects($this->any())`, you don't actually know if that method is *supposed* to be called or if it’s just there to provide data. It blurs the line between a Stub and a Mock. PHPUnit 13.2 is essentially saying: "Decide what you’re doing." Are you injecting data? Use a Stub. Are you verifying a behavior? Use a Mock with an explicit count like `once()` or `exactly()`. Host: That makes total sense, but honestly, it sounds like more work! Is the "clarity" really worth the extra typing? Guest: You know, I thought the same thing until I started refactoring a legacy suite last month. The real "aha moment" comes when a test fails. In the old way, if you had a complex mock and something went wrong, you’d get this generic "Expectation failed" message. It was a nightmare to debug. But with 13.2, because you’re forced to use `createStub()` for data injection, if you accidentally try to verify an invocation on it, the error message is incredibly specific. It says, "Unexpected method call on Stub." It immediately tells you, "Hey, you’re using the wrong tool for the job." It stops tests from being brittle because you aren't over-specifying things that don't matter. Host: Interesting! So it’s actually helping us write tests that don't break the moment we change a minor implementation detail. Now, I want to talk about the technical side. The release notes mention huge performance gains, specifically for those of us on PHP 8.4. How does being more "explicit" make our tests faster? Guest: This is the part I’m actually most excited about. So, in PHP 8.4, we have these new engine optimizations—things like property hooks and better handling of proxy classes. When you use a traditional Mock that tracks every single invocation—which is what happens when you use `any()`—PHPUnit has to maintain a pretty heavy "state object" behind the scenes to keep count of everything. Host: Right, it’s constantly watching. Guest: Exactly. But with 13.2, when you use `createStub()`, PHPUnit can allocate a much leaner object. It doesn't need to track counts. In a massive suite with thousands of dependencies, that reduction in memory overhead is... well, it’s actually quite significant. We’re seeing noticeably faster CI/CD pipelines because the engine isn't bogged down by all that unnecessary "accounting" for calls that we don't even care about. Plus, it’s using PHP 8.4’s stricter type safety to catch invalid mock configurations at the engine level rather than through runtime type-juggling. Host: Oh! So it’s actually catching errors *before* the test even runs fully? Guest: Pretty much. If you’re using DNF types or complex union types in your PHP 8.4 code, the new PHPUnit-generated proxies are much better at mirroring those. It prevents those weird "Type error" crashes that used to happen when a Mock would return something slightly off. Host: That’s a huge relief. I’ve definitely lost an afternoon to a type-juggling error in a Mock. But okay, Marcus, let's get real for a second. If I’m sitting on a codebase with 5,000 tests and half of them use `any()`, am I just stuck in deprecation-warning hell for the next year? Guest: [Laughs] It can feel that way! The warnings in 13.2 are definitely... vocal. They really want you to move toward PHPUnit 14. But the good news is the community is already ahead of this. Tools like Rector are a lifesaver here. There are already automated rulesets specifically for 13.2. It can scan your code, find `any()` matchers that don't have assertions tied to them, and automatically convert those Mocks into Stubs. Host: Seriously? That takes a lot of the sting out of it. Guest: It really does. I ran it on a mid-sized project last week and it handled about 80% of the conversions automatically. The remaining 20%—the ones where the logic was a bit fuzzy—those were the ones I *needed* to look at anyway. It forces you to have those conversations with your team: "Wait, why *are* we mocking this logger? Does the test actually care if it's called?" It’s that "tough love" we talked about. It clears out the technical debt that’s been hiding in our test suites for years. Host: I love that. It’s like a spring cleaning for your logic. So, looking forward, it sounds like the "generic mock" is basically becoming an anti-pattern. If you were to give one piece of advice to developers starting a new project on PHP 8.4 today, what would it be? Guest: I’d say: start with `createStub()` by default. Only reach for `createMock()` when you specifically need to prove that a side effect happened—like "I need to be 100% sure this email was sent." If you’re just trying to get the code to run so you can test a result, use a stub. It’s faster, it’s cleaner, and your future self will thank you when you have to refactor that code six months from now. Host: "Start with a stub." That’s a great mantra. It’s funny how the tools we use eventually shape the way we think about the architecture itself. Guest: Absolutely. Precision over convenience. It’s a bit more effort upfront, but the stability it brings to a project is worth every extra keystroke. Host: Marcus, this has been so eye-opening. I think I have some refactoring to do this afternoon! Thank you so much for joining us on Allur and breaking down why PHPUnit 13.2 is such a game-changer. Guest: My pleasure, Alex. Happy testing! Host: If you want to learn more about the PHPUnit 13.2 release or check out those Rector rules Marcus mentioned, we’ve put all the links in the show notes. This era of explicit testing might feel like a hurdle, but it’s clear that it’s paving the way for much more robust PHP applications.

Tags

software engineering open-source php testing phpunit