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::generate() or 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

  • $originalClassName The classname you want to get a mock object of
  • $methods An 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.
  • $arguments Arguments for the constructor of the created object.
  • $mockClassName The classname you want the mock object to have. If left blank one will be generated
  • $callOriginalConstructor Set to false to have the original constructor not called.
  • $callOriginalClone Set to false to have the mocker replace __clone
  • $callAutoload Use 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

Show Plain Text
  1.  
  2. $mock = new MockThing();
  3. $mock->expectAt(0, 'send', array('one', 'two'));
  4. $mock->setReturnValueAt(0, 'send', true);
  5.  

You would do something like

Show Plain Text
  1. $mock = $this->getMock('Thing');
  2. $mock->expects($this->at(0))->method('send')
  3.     ->with('one', 'two')
  4.     ->will($this->returnValue(true));

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 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 with() and 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 any() or 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 in().

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.

Comments

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 24/6/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 24/6/10

Hey Mark,

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

Jonathan on 20/1/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 25/1/11

Thanks Mark,

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:

http://www.mockobjects.com/2007/04/test-smell-everything-is-mocked.html

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?

Thanks Mark!

Jonathan

Jonathan on 1/2/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 15/4/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.

https://github.com/sebastianbergmann/phpunit/tree/3.6/PHPUnit/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 2/3/12

did you try the Phpunit plugin?
https://github.com/dereuromark/PHPUnit-Cake2

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 5/3/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 24/4/12

Have your say:

*
* You can use Textile markup, but be reasonable