Native Mocking and Stubbing in PHP
Incredible six years gone since I started doing TDD on daily basis, I did even write a unit testing framework for Magento 1.x extension developers to make testing easier. (Spoiler alert: it is focusing on a wrong problem to solve, and I don’t use for 4 years already).
From this six years, it was incredibly hard during the first one to turn my head around with unit testing, as I really didn’t do it the way it give me the real benefits. My first tests were looking like rubbish and didn’t serve any good purpose for refactoring possibilities. But after hundreds of trial and errors I finally come to the conclusion that my tests are just wrong, as I didn’t test the contract of my method, but was testing its private behaviour instead.
Even so, I was writing test before writing production code, and I was too concerned on the way how my resulting code looks like, than on its result. It took at least 2 years in total to realise that my tests were not testing, they just were mocking the production code behaviour. So nowadays I try to focus less on mocking the private behaviour than actually validating the result of the unit tests. However I mock some collaborators, but in a little bit different way then in the beginning, I found myself to transition from “mockist” to “classicists” style over this 6 years. You can learn more about both styles at this nice article by Martin Fowler.
Mocking Balance: Too much vs Too little
My general rule of thumb: mock any external collaborator, but use internal collaborator directly if it does not cross architectural boundary (IO or another component).
I was using both PHPUnit MockObject and Prophecy but all of them required me to speak their language, that is not the language of my applications. The last one is even extremely addictive as it is so simple to mock, that you prefer to mock than not to mock.
Mocking Frameworks Performance
In the end, mocking any kind of class or interface in PHP by frameworks requires quite expensive usage of reflection, as without reflection declarative dynamic mocks are impossible. So if you have a large suite of 1000 unit tests where every single collaborator is mocked you have quite a hit in performance. I know you’d ask for proof, so I created a small experiment with the most simplest mocking and results were very interesting. Every test case class is running 1000 repetitions.
PHPUnit MockObject ~ 650ms (4M)
Prophecy ~ 1,7 s (18M)
Concrete Mock Class ~440ms (4M)
Anonymous Mock Class ~ 440ms (4M)
Self Shunting ~ 440ms (4M)
As you see mock frameworks add quite an overhead on a very simple mock object. There is also a very big difference between Prophecy and MockObject, as latter one does apply some caching for the same class creation (i.e. leaves side effect), so it can skip reflection next time. In real project situation both of them will behave like Prophecy as mocked collaborators are very diverse.
Is Native Mocking hard?
Probably you are asking this question to yourself and imagining a lot of nested conditions and instanceof checks across your test code base. But no one prohibits you to use native PHPUnit assertion library in order to test your unit tests. In the end native mocking is more readable and easier to trace, comparing to mocking libraries.
Of course code samples are better then 1000 words, so lets compare all of the code samples.
As an example we will use such an external collaborator for mocking:
A consumer of this collaborator might look as simple as that:
Here is what the conventional unit test using the MockObject framework might looks like:
Now lets compare it to most common techniques of mocking without using any frameworks.
This the most simple method to create a mock, as you don’t need to create any external class, your test class in this case plays a role of a mock/stub. This method is possible to do in PHPUnit only if your test double is an interface, as for obvious reasons there is no multiple inheritance in PHP.
I usually use Self Shunting when I just start my test case, as I don’t want to switch between files. But with time my test gets refactored and shelf shunted mock usually gets decoupled into concrete class.
This one is very similar to Shelf-Shunting, but it gives more better look to your test, as all that mocking is extracted from your test case, and more important your mocks become re-usable units on its own.
Just take a look at this mock class:
As you see expected arguments are parameters passed via constructor, so you can re-use the same class for different tests and keep your tests DRY.
So here is just a sample how it is used in test case:
Apart from test code simplicity, this approach also gives benefits in case of test failure, as you see exact trace for the error, without hundreds of mocking framework calls in the stack trace.
Anonymous Classes (≥PHP7)
In case if your project has minimum version requirement for PHP7.0, than you are lucky enough to use anonymous classes for creating mocks. This methods gives you more freedom and flexibility in edge cases, but also scatters your test code with awkward nested structures of internal class.
The difference in performance between concrete class and anonymous one is almost unnoticeable, so it is a great alternative for inlining your complex edge cases with possible future refactor options into a concrete mock class.
Here is an example of such approach with the mentioned before test case:
So are Mock Frameworks useful after all?
I cannot answer definitely on this question, as this one is question of taste. But what I can tell definitely, that without mocking framework my tests tend to be simpler and code gets more clean, as I start noticing code-smells on earlier stages of development.