PHPUnit with Zend_Controller_Action_Helper

I'm currently working on a REST service built in Zend Framework and I ran into problems very quickly - when I tried to write the first unit test for the first action in fact! PHPUnit was just dying when I asked it to dispatch() any URL which didn't return HTML, it wasn't even giving its usual output.

What was actually happening was I was making use of Zend_Controller_Action_Helper_Json, my service returns JSON and this action helper takes input, transforms it into JSON, sets the content-type correctly and tells ZF not to look for a view since we don't need one. I thought this was pretty neat.

But look at the start of the declaration for the action helper:

class Zend_Controller_Action_Helper_Json extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * Suppress exit when sendJson() called
     * @var boolean
     */
    public $suppressExit = false;
...

By default the JSON action helper will exit() - which of course when run from phpunit causes that to exit as well! There is the class variable there so it was simple to turn off - I just extended my class and changed the value of that $suppressExit variable.

class Zend_Controller_Action_Helper_ServiceJson extends Zend_Controller_Action_Helper_Json {
    public $suppressExit = true;
}

My tests now run successfully and I can build my application, hopefully next time I'll realise what I'm doing wrong faster!

11 thoughts on “PHPUnit with Zend_Controller_Action_Helper

  1. I had the same problem recently and it was hard to find.

    I think you should use
    Zend_Controller_Action_HelperBroker::getStaticHelper('Json')->suppressExit = true; in your test script instead of overriding the class

    • Maxence: Ah ha, that's a good tip, thanks! I actually put some other changes into my extended action helper as well but for anyone only using JSON you make a nice point for testing.

  2. Things like that happen when you don't let the view generate all output. This is in my opinion a design flaw in ZF.

    Generating JSON from data is the view's job, whether the view uses a helper to accomplish that does not really matter, but if no view is involved, that is definitely wrong.

    • Dying on dispatch is somewhat surprising behaviour! Good catch. It's not mentioned in the documentation, either.

      There is actually also a JSON view helper (used by the JSON action helper) that would seem to do what Stefan wants. (It disables layouts, sets the header, but otherwise doesn't interfere with dispatch.)

      I'm still thinking about this, but I don't have any particular problem with a JSON action helper that in some sense "bypasses" a view. (Is this your complaint?) What would the view do in the case of JSON output? It wouldn't do anything very exciting, and you'd end up having to duplicate a bunch of boring boilerplate code.

      • Actually I think the action helper is quite neat and I was pleased with it saving me from writing duplicate, mostly empty views. I just got into a mess when I tried to test it!

    • I agree on that, there is certainly something wrong when you're formatting data to send as a response in your controller. It should happen in the View instead and as Stefan said, it could well be considered a design flaw.

      But what I find even more hideous is the fact that ZF actually uses an exit; statement to break out of the "normal chain". I can imagine it takes quite a while to debug this, and it makes absolutely no sense.

  3. Here's a much leaner solution to the same problem, bypassing the built-in ZF action helper (which calls the view helper with the same name, which calls Zend_Json and does way too many smart things for my tastes):

    [geshi lang=php]
    class My_Controller_Action_Helper_Json extends Zend_Controller_Action_Helper_Abstract
    {
    /**
    * Strategy pattern: call helper as helper broker method
    *
    * Sends a JSON-encoded response with the payload
    *
    * @param array $payload
    *
    * @return string|false
    */
    public function direct(array $payload)
    {
    $json = json_encode($payload);
    $response = Zend_Controller_Front::getInstance()->getResponse();
    $response->setHeader('Content-Type', 'application/json');
    $response->setBody($json);
    return $json;
    }
    }
    [/geshi]

    Make sure noViewRenderer is set to true in the front controller.

Leave a Reply

Please use [code] and [/code] around any source code you wish to share.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>