dagfinn | 10 June, 2008 13:40
I came across a Zend Framework (ZF) example I wanted to refactor. You really have to have unit test coverage to refactor effectively, and since there were no tests, I started trying to find out how to test it. There didn't seem to be a wealth of information available on the web, so I've tried to figure it out by myself.
Several challenges presented themselves. It might seem as if the ZF controller system is not optimally designed for testing action controllers, but there are always ways to get around obstacles.
A ZF action controller (which is really just a group of actions collected in a class) always extends Zend_Controller_Action. (For an introduction, see the official QuickStart).
The one I was playing with was an authentication controller:
class AuthController extends Zend_Controller_Action...
That's all we need to know about the specific class under test for now.
I always like to set up the basic objects for the test as instance variables in the setUp() method, so that I can write many small test methods exercising them in various ways. To even get started testing an action controller, we need to instantiate it outside its usual environment, the Zend Front Controller. It requires a request object and a response object:
class AuthControllerTest extends UnitTestCase { function setUp() { $this->request = new Zend_Controller_Request_Http(); $this->response = new Zend_Controller_Response_Cli(); $this->controller = new AuthController($this->request,$this->response); }
(I'm using SimpleTest here, but the same techniques should apply to PHPUnit with different syntax.)
The challenge increases increase when we want to use View Helpers, such as the flash messenger. When programming the action controller, it looks like this:
$flashMessenger = $this->_helper->FlashMessenger;
To test this meaningfully, we need to replace the flash messenger with a mock object. That means also having to replace $this->_helper, which is a "helper broker" object.
First, we generate the mock classes:
Mock::generate('Zend_Controller_Action_HelperBroker','MockHelperBroker'); Mock::generate('Zend_Controller_Action_Helper_FlashMessenger','MockFlashMessenger');
Unfortunately, the helper broker is a protected instance variable, with no apparent way to change it. But it's easy to fix by adding a setter method to the action controller class:
class AuthController extends Zend_Controller_Action... public function setHelperBroker($helperBroker) { $this->_helper = $helperBroker; }
This allows us to set up the mock helper broker and let it return the flash messenger:
$this->helperBroker = new MockHelperBroker; $this->controller->setHelperBroker($this->helperBroker); $this->flashMessenger = new MockFlashMessenger; $this->helperBroker->setReturnValue( '__get',$this->flashMessenger,array('FlashMessenger'));
Now we can use the mock objects to test an action which is supposed to send a flash message:
function testIdentifyActionSendsFlashMessage() { $this->flashMessenger->expectOnce( 'addMessage', array('Please provide a username and password.')); $this->controller->identifyAction(); }
That's as far as I'm going with this now. There may be more later.
dagfinn | 02 June, 2008 21:12
Redirects are useful in web programming, especially when implementing the Post-Redrect-Get pattern. But there is a problem with redirects: there is no simple way to send a message to the user across the redirect. When processing a GET request, you can display whatever messages you want. The most simplistic way is to echo them directly; or if just slightly more sophisticated, set it in the template that's about to become the web page. When processing a POST request that is to be followed by a redirect, you can't do that. The response (redirect) sent back to the browser does not have any text or HTML content. In practice, it just contains the URL of the page you're redirecting to. If you try echoing the message, it will cause the redirect to fail because you sent content before the header that signals redirect.
So how to get the message displayed? The simplest way to do it would be to include it in the URL:
header('Location: http://www.example.com/index.php?message='.urlencode($message));
It's possible, but your URLs can get very long and you might find it silly:
http://www.example.com/index.php?message=This+is+a+message+to+demonstrate+how+long+a+URL+can+get+when+you+put+long+strings+into+it
The more common choice is to keep the message in session and make sure it's removed from session after it's been displayed. That's what flash messages do. In military terminology, a flash message is defined as:
A category of precedence reserved for initial enemy contact messages or operational combat messages of extreme urgency.
I don't like a name that could be confused with Adobe Flash, so when I implemented my version of this, I looked up "flash" in the thesaurus and decided to call them flares instead.
Here are a couple of examples I've picked up.
Zend Framework:
$flashMessenger = $this->_helper->FlashMessenger; $flashMessenger->setNamespace('actionErrors'); $flashMessenger->addMessage($message);
CakePHP (from http://labs.iamkoa.net/2008/01/13/session-based-flash-messages-look-better-cakephp/):
$this->Session->setFlash('Your post has been saved.'); $this->redirect('/news/index');
In my own work, I've done it in a slightly different way by letting the redirect object hande the flash/flare and returning the redirect object from the action method, somewhat like this:
return Redirect::toAction('settings')->addFlare('Settings changed');
I don't necessarily claim that it's better than the others, but it makes sense to have alternatives.
This article was originally posted with an incorrect timestamp, and has been updated to the current time as of June 2.
dagfinn | 02 June, 2008 12:46
I keep saying that sort of thing, too. I also say it raises your IQ. But when I attended a presentation by Uncle Bob (Robert C. Martin) last summer, I was mildly shocked to see that he described TDD as tedious (but a requirement for professionalism). I confronted him politely about it, emphasizing that above all I find TDD relaxing. Why? Because bugs bug me. I get stressed when I have to search for a bug and even more so when it takes much longer than expected (I'm sure that never happens to you). With TDD, bugs are always small and easy to locate.
Uncle Bob seemed to agree with me as far as his own experience was concerned, but he believed that many developers found the non-TDD process exciting rather than stressful.
Sounds weird to me. But I know from long experience that sometimes people are different from me in ways that make reality seem stranger than fiction.
| « | June 2008 | » | ||||
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | |||||