Testing CakePHP controllers - Mock Objects edition

I recently wrote an article about testing CakePHP controllers the hard way where I covered testing controllers by running their methods manually. I hinted at some additional tricks that could be performed by using Mock Objects. Today I’m going to spill the beans on Mocks, and how I use them when testing my Controllers.

What is a Mock Object

First figuring out what Mock objects are and are not is important. The wikipedia says

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.Wikipedia

In unit testing we use mocks both as a way to isolate our unit (the object being tested) from the world and to allow us to create unexpected situations. Ever wonder how your application would react if a random value was injected, or wonder how you can easily trigger that exception that relies on FooBarComponent returning false which it only does if the file system is corrupted? Well these are both situations that can be simulated with Mock Objects. Mock objects can also take an active role in Unit testing and make contribute additional assertions to your tests, in the form of expectations. Mocks are not the solution to all your testing woes, nor will they make a good cup of coffee, but moving on.

Where can I get one of these fabulous devices?

Well since we are working in the context of a CakePHP unit test and therefore using Simple Test. We get Mock objects from the Mock class. We generate Mock objects from existing classes, and due to the magic of Reflection and eval() a Mock object class is generated. The simplest example would be:

Show Plain Text
  1.  
  2. App::import('Component', 'Email');
  3. Mock::generate('EmailComponent');
  4.  
  5. //Then when we need it
  6.  
  7. $this->Posts->Email = new MockEmailComponent();
  8.  

This will generate a class called MockEmailComponent. This MockEmailComponent will have all the same methods as our real EmailComponent. One big difference between the real object and the mock is that all of the mock methods do not work. They all return null and take any number of arguments. But hold on a second, if its methods don’t have returns what good are they? Well lots, because you can set the return value or return reference value of all its methods.

Show Plain Text
  1. $this->Posts->Email->setReturnValue('send', true);

This will set the return value of send() to true. However, it will not send an email, which is the nice part. Because getting emails from your test suite is never fun. Also it allows the test to run on machines that don’t even have e-mail servers, like a development box. Using a Mock to test email being sent also allows you to test what happens when your email server is down or other difficult to simulate situtations. Generating a Mock object also allows you to append extra methods onto your objects, such as those your are going to build but haven’t. This would look a little something like

Show Plain Text
  1. Mock::generate('EmailComponent', 'MockEmailComponent', array('slice', 'dice'));

We’ve now added the methods slice() and dice() to our MockEmailComponent. In addition to complete mocks, we can build partial Mocks. A partial mock is just what it sounds like. It has only a few of its methods mocked. The rest stay as is in the declared classe(s). This is really handy for Objects that use only a few methods to write to a resource. An example of this would be your controllers. In my previous article I used a subclass to dummy out the render and redirect methods. However, we could also do this with partial mock objects.

Show Plain Text
  1. Mock::generatePartial('PostsController', 'TestPostController', array('render', 'redirect'));

In our tests we can now use Mock expectations to assert that our redirects and render calls are occurring properly.

Makings expectations with Mock Objects

Mocks can be used to feed your application values as seen above. Furthermore, Mock Objects can be used to introspect on your Unit and ensure that it is properly delegating / calling methods on its inner objects. Say for example we wanted to test the use of SessionComponent::setFlash(). Now, we could not mock it, and make an an assertion before and after the method has run to test that the value in the session was not set, and then is set. This will work just fine until we start adding lots of test methods that use the session. We could easily run into bleed-through between our tests, causing our tests to become dependent on the order in which they are run, or worse yet create broken tests. This is no good. Furthermore, using the real session will nuke any sessions we have with the box we are testing on. Using a Mock object for our Session solves all of these problems.

Show Plain Text
  1. //Import and generate the mock we want.
  2. App::import('Component', 'Session');
  3. Mock::generate('SessionComponent');
  4.  
  5. //In one of our test methods
  6. $this->Posts->Session = new MockSessionComponent();
  7. $this->Posts->Session->setReturnValue('read', 1, array('Auth.User.id'));
  8. $this->Posts->Session->expectOnce('setFlash');
  9. $this->Posts->admin_edit();

In the above we’ve not only set a return value for our SessionComponent’s read() method when passed the argument of Auth.User.id but we’ve also made an expectation that setFlash() will be called exactly once. If it is called twice or never this assertion will fail, and we will get the red bar of doom. Notice that I didn’t make an assertion on the value being passed to setFlash(). You totally can expect certain parameters to be passed to mock methods. However, I find setting assertions for the values being fed into methods like setFlash() can be subject to a lot of change. If we were to make assertions on these inputs, we would need to update our tests each time the message changes. I personally find I’m more interested that the method is called, giving the user feedback then what the exact contents of that feedback are.

There is so much that can be said about mock objects. However, this post is long enough for the time being. Be sure to check out the SimpleTest Documentation on Mock Objects for a complete reference on the API and more additional information about SimpleTest mock objects.

Comments

Thanks for this post. It’s very clear on how to use Mock objects and a very useful introduction to the simpletest chapter on the subject from a cakephp perspective.

Fran Iglesias on 21/1/09

I had no idea any of this existed.

Just when I start to think I’m a Cake master, I read something like this that puts me in my place.

Matt Curry on 21/1/09

Thanks for the great articles on Controller testing…they’ve been a huge help. I’m having trouble mocking up the controller objects themselves, though.

I’ve got a controller action that calls several functions elsewhere in the controller, and since I’m unit testing, I wanted to isolate the action being tested. So I wanted to do a partial mock of the controller object so that those functions wouldn’t be called. Here’s what I have:



Mock::generatePartial(‘AggregatorController’, ‘MockAggregatorController’, array(‘getCombosDueForRefresh’, ‘refreshCombos’, ‘saveCombos’));
$this->Aggregator = new MockAggregatorController();
$this->Aggregator->constructClasses();
$this->Aggregator->Component->initialize($this->Aggregator);

$this->Aggregator->expectOnce(‘getCombosDueForRefresh’);
$this->Aggregator->expectOnce(‘refreshCombos’);
$this->Aggregator->expectOnce(‘saveCombos’);

$this->Aggregator->collect();

I get an error when constructClasses() is called though:


Fatal error: Call to a member function init() on a non-object in /Applications/xampp/xamppfiles/htdocs/project/cake/libs/controller/controller.php on line 413

Any ideas? Is my approach way off? Thanks again for your help!

Ryan W. on 16/2/09

Ryan W: You’ve stumbled upon one of the toughies with mocks in simple test. You have the right approach though. The problem comes from the Mock object itself. A partial mock overloads the __construct() of the mocked object but never calls parent::__construct() So your controller object is never properly built. There are a few ways around this. One is to define your own mock class and tell simple test to use that before you generate your partial mock. Another is to emulate what Controller::__construct() does and build Component and popluate $methods manually. Both are not fun, I’ve done the custom mock approach and it is tricky to get working.

mark story on 20/2/09

Sorry, but the cursive font is very hard to read….

Sans Serif on 24/3/09

This is great, Mark. I’ve been using JMock on a different project and missing that capability in my PHP work. This makes me happy!

Dave Cassel on 15/4/09

I consider what mark comments about simpletest partial mocking a bug.

I filed it at:

https://sourceforge.net/tracker/?func=detail&aid=2820721&group_id=76550&atid=547455

and a patch is attached there.

lumen on 13/7/09

Nice post. I had same error as Ryan W. Overwriting and a bit of hacking solved it.

Abhimanyu Grover on 2/9/09

It’s worth mentioning that the above mock email example will throw a PHP notice when the tests are run.

This can be rectified with the following:

$this->Posts->Email = new MockEmailComponent(); $this->Posts->Email->enabled = true; $this->Posts->Email->setReturnValue('send', true); // etc.

Without this you’d get an undefined property notice on MockEmailComponent::enabled, and exceptions make my tests sad.

Ben Murden on 4/6/10

Is it possible to create mock object out of Model’s Behavior?

Ken on 16/8/11

I am running into the same thing that Ryan W was seeing. I am afraid I do not understand your comment Mark. Could anyone provide an example of how to get around this?

Jon R on 4/1/12

Nevermind! I do not know if it will work for all cases but I just literally copied the logic from Controller.__construct() into a controller base class and modified all references to ‘this’ to ‘this->controller’. ‘controller’ is a variable I created to hold a reference to the controller I am testing. When I create a controller test I have to set the variable in startTest() and then call parent::startTest() which holds all of the logic from Controller.__construct().

Jon R on 6/1/12

Have your say:

*
* You can use Textile markup, but be reasonable