I recently spent some time digging into the PHPUnit mocking framework to clarify my own understanding and better decide how we can improve our unit testing tools. I’ve found the PHPUnit documentation on the subject to be inadequate for anything but the most basic use cases. (The full API documentation does not include the mocking framework, as far as I can tell.) I’ll try to clarify the one aspect of this that confused me the most and caused me to lose the most time.
Stubs vs. Mocks
This is touched on in the documentation, but I’m not a fan of the way it’s described. “Mocks” are objects which have some of their methods replaced by stubs (methods with no behavior). “Stubs” are objects which have stubs for all methods, with expectations optionally placed on certain methods. As far as I can tell, the distinction between a mock and a stub comes when you provide a list of explicit methods to stub, or not. When you provide a list of methods like so:
$mock will defer to the original class’s methods for all method calls except for “methodOne” and “methodTwo”. These methods will always result in successful calls, but will not return any values.
When you don’t provide any methods, you get a stub with no functionality. This object has the ability to act in place of the mocked class wherever it is used, but has no functionality on its own. Once you create this object, you can add expectations and return values to force it to behave in ways that are useful to you:
This strategy allows you to build piecemeal mocks that build functionality you need as you go. I find this the most useful way to use PHP object mocking. If you’re creating objects which need to be “partially mocked” in order to test, you might want to reevaluate the structure of your objects–possibly moving those partially mocked methods into another component, which can then be fully stubbed.
At RJMetrics, I’ve been experimenting with a mock dependency container which will provide stubs for all our objects. This will give us the greatest flexibility in terms of being able to add units of functionality needed for a specific test, while still maintaining internal consistency with all of our dependent objects.