In modern unit testing, the clarity of your test code is just as important as the logic itself. For years, PHP developers using Mockery have relied on shouldReceive() as a "catch-all" for test doubles. However, as the library has evolved, a clearer distinction between Stubs and Mocks has emerged that makes tests more readable and less prone to false positives.
This need for this shift was highlighted recently by Joel Clermont in his article, “Don’t forget this when writing mock assertions”. He pointed out a common pitfall: using shouldReceive() without an explicit expectation like once(). In these cases, Mockery defaults to "zero or more" calls, meaning your test might pass even if the method is never actually executed.
1. The Fundamental Difference
To write better tests, we must distinguish between how data enters your system and how signals leave it.
- Stubs (Indirect Input): These provide the "canned responses" (data) your application needs to function. You don't necessarily care if the method is called once, twice, or at all—you just need it to return a specific value when it is called.
- Mocks (Indirect Output): These verify that your system performed a specific action, such as sending an email or saving a record. The verification is the goal of the test.
2. Implementation: allows() vs. expects()
Mockery v1 introduced a more expressive syntax to replace the ambiguous shouldReceive().
Scenario A: Stubbing with allows()
Use this when your test object needs data from a dependency. For example, a repository finding a record:
// We are providing data so the application can continue (Input)
$userRepository->allows()->find(123)->andReturns($user);
Why use this? Using allows() signals to other developers that this is a dependency setup, not a strict requirement. If your implementation changes and calls find() twice instead of once, the test won't break unnecessarily. It is resilient to refactoring.
Scenario B: Mocking with expects()
Use this when you must verify that a side-effect occurred. For example, ensuring a notification was dispatched:
// We are verifying that an action took place (Output)
$paymentGateway->expects()->charge($amount);
Why use this? The expects() method automatically sets a default requirement that the method must be called exactly once. It eliminates the risk mentioned by Joel Clermont where an assertion is forgotten. If the method isn't called, the test fails.
3. Comparison Summary
| Feature | Mockery method | Primary purpose |
|---|---|---|
| Stub | $m->allows() | Provide data to the test |
| Mock | $m->expects() | Verify an action happened |
| Legacy | $m->shouldReceive() | Ambiguous / Combined |
Why stop using shouldReceive()?
While shouldReceive() is still working, it lacks intent. When a developer reads a test using shouldReceive(), they have to scan the rest of the chain for once() or andReturn() to understand what is being tested.
By switching to allows() and expects(), the intent is communicated immediately. You clearly separate the setup of your environment from the assertions of your behavior.
Top comments (0)