PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
166: Mocking Objects with PHPUnit Mock Builder
I've noticed a recurring pattern when I review code from developers moving into professional TDD: they treat mocking as a way to simply "get the code to run" without crashing. They often create a whole directory of "Fake" classes—like FakePaymentGateway or MockUserRepository—manually implementing interfaces just to return a hardcoded value.
Thinking Manual Fakes are Easier than Mocks
When you manually create a fake class, you're essentially writing more production code just to test your production code. Let's say you have a PaymentGateway interface. You might write a FakePaymentGateway that always returns true for a charge() method. It works, but it's brittle. If you add a new parameter to the charge() method in the interface, your test suite suddenly breaks in ten different "Fake" classes. You've just doubled your maintenance burden.
// The "Manual Fake" trap - avoid this for simple behavior
class FakePaymentGateway implements PaymentGateway {
public function charge($amount) {
return true; // Hardcoded and inflexible
}
}
Using the Mock Builder to Verify Interactions
The real power of the PHPUnit Mock Builder isn't just providing a return value; it's behavior verification. I don't just want to know that my OrderProcessor returned true; I want to know that it actually called the charge() method exactly once, and that it passed the correct dollar amount.
Here is how we do this using the Mock Builder. Instead of a separate class, we generate the object on the fly:
public function testOrderProcessesPaymentCorrectly()
{
// 1. Create the mock
$paymentMock = $this->createMock(PaymentGateway::class);
// 2. Set the expectations
$paymentMock->expects($this->once()) // I expect this to be called exactly once
->method('charge') // This specific method
->with(99.99) // With this exact argument
->willReturn(true); // And it should return this value
$processor = new OrderProcessor($paymentMock);
$result = $processor->process(99.99);
$this->assertTrue($result);
}
Notice the shift in logic. We aren't just creating a "dumb" object; we are defining a contract for this specific test. If the OrderProcessor accidentally calls charge() twice, or calls it with 0.00, PHPUnit will fail the test immediately, even if the final result is true. This is where you catch the subtle bugs that manual fakes miss.
Handling Complex Return Sequences
Sometimes, a single return value isn't enough. You might have a loop that checks a status multiple times. I used to struggle with this until I discovered willReturnOnConsecutiveCalls(). It's a lifesaver when you're testing polling mechanisms or retry logic.
$paymentMock->method('getStatus')
->willReturnOnConsecutiveCalls('pending', 'pending', 'completed');
In this scenario, the first two times your code calls getStatus(), it gets 'pending'. The third time, it gets 'completed'. This allows you to test that your loop actually iterates and doesn't just exit early or hang forever. It's clean, it's inline, and you didn't have to write a single extra class file to achieve it.
📋 Practical Task
Exercise: Testing the NotificationDispatcher with a Mocked SMS Gateway
You have a NotificationDispatcher class that depends on an SmsGatewayInterface. Your goal is to ensure that the dispatcher only sends an SMS if the user has opted-in to notifications.
The Requirements:
- Create a test case where a user has
optIn = true. Verify that thesendSms()method of theSmsGatewayInterfaceis called exactly once with the correct phone number. - Create a second test case where a user has
optIn = false. Verify that thesendSms()method is never called.
Starter Code:
interface SmsGatewayInterface {
public function sendSms(string $number, string $message): bool;
}
class NotificationDispatcher {
private $gateway;
public function __construct(SmsGatewayInterface $gateway) {
$this->gateway = $gateway;
}
public function dispatch(User $user, string $msg) {
if ($user->isOptedIn()) {
return $this->gateway->sendSms($user->getPhone(), $msg);
}
return false;
}
}
Implement the PHPUnit test class using createMock(), expects(), and with() to validate these two behaviors.
There are no comments for now.