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:
- App::import('Component', 'Email');
- //Then when we need it
- $this->Posts->Email = new MockEmailComponent();
This will generate a class called
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.
- $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
We’ve now added the methods
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.
- 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.
- //Import and generate the mock we want.
- App::import('Component', 'Session');
- //In one of our test methods
- $this->Posts->Session = new MockSessionComponent();
- $this->Posts->Session->setReturnValue('read', 1, array('Auth.User.id'));
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.