Stop ACTA

Unit test your CakePHP controllers like a noob

Posted in CakePHP on 29.06.2012.

CakePHP makes it easy for you to test your code, so here are some tips and gotchas for controller tests.

While writing tests for the new Feedback plugin, I learned a few things about cake's testing framework and PHPUnit.

Below are some general guidelines, but first, if you haven't done so, please go ahead and read The Cookbook on testing controllers because this is not the all-encompassing tutorial you may be looking for.

First of all, make sure your test class extends ControllerTestCase, not the plain-old CakeTestCase. This gives you access to the awesome generate() method, which will mock your controller methods when necessary, but more importantly, it will mock the week out of the surrounding stuff like components and models.

Like this:

$this->generate
    (
        'Feedback.Ratings',
        array
        (
            'models' => array
            (
                'Feedback.Rating' => array('create'),
                'Article' => array('read'),
            ),
            'components' => array
            (
                'Feedback.Ratings',
            ),
        )
    );

This is great stuff because you can completely isolate your controller and make real unit tests.

However, be carefull about one thing: if you mock only a few methods of your object, the rest of the methods are the original methods. In the example above, I've mocked the method Article::read(), so Article::find() (for example) is the real find method, and it will attempt to query your database.

When things start to get freaky, make sure you're not calling a non-mocked method when you did not intend to do so.

Next, you obviously test your controller by calling testAction():

$result = $this->testAction
    (
        '/feedback/ratings/add/Arse/1',
        array
        (
            'data' => $data,
            'return' => 'headers',
            'method' => 'post',
        )
    );

There is, however, a gotcha when doing this in combination with generate().

If you try to call testAction() several times in a row in a single test action, you're going to have a bad time. Why? Because testAction will try to re-mock your controller, provided that autoMock property is true (and by default, it is).

While this is a good thing, testAction is not the brightest kid in the class. It will re-mock everything, but it will not do it the way you did, with your settings. In other words, what you wanted to be a mock object may not be a mock object at all. Your expects() will fail on things like CakeRequest methods, and you'll have no idea why. This means it will pretty much mess up the test badly, and your hair will fall out while you try to find out what went wrong (not that you have any hair now, right?).

In other words:

don't use testAction() more than once per test

Of course, this is a good advice regardless of this "feature", because it is a good practise to test only one thing per test, and different calls to testAction are not testing the same thing by definition.

Last but not least, you may have trouble finding out how to simulate things like AJAX requests, client IP address etc.

Fortunately, there's a way to do that too; you can simply do this in your setUp():

$_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';

Et voilà! Hackish? Yes. But it works, and that's good enough for now.

Happy baking!

Article comments — View · Add


Page 1 of 1

Alex :: 02.06.2014 11:52:41
+1 for the mock the week reference.