Getting familiar with PHPUnit Mocks
As you may or may not know CakePHP is transitioning to PHPUnit and with this transition comes a totally new Mock object interface/implementation. After porting some intensive mock object tests, I thought I would share what I’ve learned about the differences and similarities between the mocks in SimpleTest you may be familiar with.
Things that are the same
The basic premise of mocks is exactly the same between PHPUnit and SimpleTest. Mocks are used to stub out object method calls and used to assert that specific methods are called. Both frameworks use code generation to create subclass definitions and
eval() to make the usable mocks. Both frameworks allow you to create critic and actor mocks. That is about where the similarities end though, method calls to mocks can often be translated fairly easily and there are some example regular expressions available in the CakePHP lighthouse wiki.
A different way to the same ends
While the basic goals of both Mock object implementations is the same the means by which you get there are very different. With SimpleTest you would statically call
Mock::generatePartial() to create a mock subclass and then manually construct it. The SimpleTest has a big drawback in that you lose your constructor when mocking. This was fairly annoying and caused me grief in a number of occasions. Thankfully PHPUnit’s mocks allow you to use constructors like normal. Instead of calling a static class you create mocks by calling
getMock(). This method takes a pile of parameters and lacks really solid documentation. The parameters for
getMock() are as follows
$originalClassNameThe classname you want to get a mock object of
$methodsAn array of methods you wish to mock out. These can either be methods in the class, or new methods. Leaving this as an empty array will mock all the methods.
$argumentsArguments for the constructor of the created object.
$mockClassNameThe classname you want the mock object to have. If left blank one will be generated
$callOriginalConstructorSet to false to have the original constructor not called.
$callOriginalCloneSet to false to have the mocker replace
$callAutoloadUse autoloading to find the class to load.
The next big difference is how you use mocks. Unlike SimpleTest’s mocks, PHPUnit’s mocks have a fluent interface. This can make for easier to grok code in some cases. Instead of
- $mock = new MockThing();
- $mock->expectAt(0, 'send', array('one', 'two'));
- $mock->setReturnValueAt(0, 'send', true);
You would do something like
- $mock = $this->getMock('Thing');
- ->with('one', 'two')
PHPUnit splits mocks into a few separate parts. First in the method chain is the
expects(), which accepts a matcher as its parameter. The built in ‘matchers’ are
once()Will fail if the method is called more than once, or less than once.
never()Will fail if the method is called ever.
any()Will always match
at($index)Will match at call $index. A very important and possibly irritating difference between the SimpleTest implementation is the index increments each time a mock method is called, not just when the indicated method is called.
exactly($times)Will only pass if the method is called $times. I find this one doesn’t work well with
atLeastOnce()Will pass if the method is called more than once.
Usually, the next thing in the chain is a
method($methodName) call, which specifies which method the expectation is for. Following
method() is usually one or both of
will(). Parameter expectations are supplied as parameters to
with(). You can either supply the literal values you are expecting, or one of the many constraint objects PHPUnit has. Return values for mocked methods are set with
will(). Will accepts a ‘stub’ as its parameter, and there are several built in stub types.
returnValue($value)Return a literal value, like a string or an array or a boolean.
throwException($exception)Make the method throw an exception.
returnArgument($index)Return the argument at $index
returnCallback()Allows you to return ‘callback type’ results. For example in PHP5.3 you can return anonymous functions.
onConsecutiveCalls()Allows you to setup a return value ‘script’. This allows you to setup a number of return values that will be returned in the sequence they are provided. This stub works best with either
atLeastOnce()in my experience.
Overall, I found the fluent interface took only a short while to get used to. The difference in how
at() works threw me off for a bit, but once I figured it out it made sense. I find the fluent interface quite readable and offers some features that SimpleTest does not. For me, the most interesting of these was
$this->onConsecutiveCalls(), which allows you to ‘script’ the return value of a method each time its called. I found this really helpful when writing tests for shell classes where I wanted to script the return values of
While fairly different from the mock objects in SimpleTest, I find that the mocks in PHPUnit offer a few more features and one more good thing that the transition to PHPUnit affords.
Thank you for posting this. My CakePHP bread and butter is mocks. I almost always mock Session, Auth, and Cookie when testing controllers.
I admit I haven’t looked into PHPUnit a whole lot, but from what I hear/read its a good move from SimpleTest.
I’m a little confused on how I’d go about mocking in PHPUnit, but this helped clear the cloud. I wont fully feel comfortable with it until I start writing tests in it.
Maybe a full example would be helpful.
Would you mind terribly showing an example of how you’d mock Auth/Session in the new PHPUnit? Much like you did in your previous blog “Testing Controllers the hard way”. As that was extremely helpful.
Nick on 6/24/10
Thanks for this Mark.
I’d like to use the PHPUnit for integration with Continuous Integration.
Do you think in the end it will be possible (maybe with a hack) to use the PHPUnit support into a Cake 1.23 app?
kvz on 6/24/10
First off, thanks for your amazing efforts with CakePHP. So appreciated, you will never know.
Is there anyway to mock a vendor class? I am using the phpseclib library for its SFTP methods and would like to mock them in my controller tests. Basically, I want to simulate the login, chdir, nlist, get, delete, etc.
Thanks very much for any guidance here. Hopefully it will help others as well.
All the best,
Jonathan on 1/20/11
Jonathan: You can mock non-static methods on any class that is loaded. To mock vendor classes, load them and use getMock(). If the methods you want to mock are static you’re kind of boned. :(
mark story on 1/25/11
What I ended up doing is creating a model with methods for each of the actions in the SFTP library, based on the advice here:
Worked like a charm, much easier to test and actually a better solution overall.
Two more questions, if I may: – I am testing plugins and have to use requestAction for inter-plugin communication. Is there any way to mock the requestAction call?
- It seems the find convenience methods (e.g. findById, findAllByName, etc) cannot be mocked? I had to convert all of them to the long-hand find with the appropriate arrays. Is there a way to mock these convenience finders?
Jonathan on 2/1/11
Not only is this a comprehensive review of how to use PHPUnit with CakePHP, but it is the most complete document for using mocks in PHPUnit – thank you very much.
frak on 4/15/11
Which version of phpunit are you using for this and how did you get it ?
My mockobject class doesn’t contain the method() , with() and will(). The current latest version of php unit on git doesn’t have the mockobject in the framework.
I also looked on http://pear.phpunit.de/
The latest version of the framework does’t contain the mockobject folder and the latest mockobject version doesn’t have the above mentioned functions.
Any suggestions ?
Howard on 3/2/12
did you try the Phpunit plugin?
it contains the latest Phpunit modules and will grab the MockObject, as well.
cheers Mark Scherer
mark on 3/3/12
okay , that one is specifit for the cake framework but
I have to use zend framework in this project.
Do you know anywhere else I might find these ?
Howard on 3/5/12
@Howard, MockObjects are in their own repository on Github. If you install PHPUnit properly, using PEAR, then it will automatically install MockObjects.
Reuben on 4/24/12
Brooksie on 10/2/12
Very nice post I can say that i am on cake almost 2 years , and i do not think that Symfony is so gatere , after few hours on Symfony two bugs were found by me . Moreover first i found out Cake only later Symfony i am from Eurpoe so Ismael i do not think that EU is mostly prefer symfony YAML is very nice for optimisation your requests to DB , but i think that using YAML structure to define smth in framework core is just waste of your time .
Prasad on 6/13/15