Testing ZF Controllers and Model interactions
Posted: Fri Jan 11, 2008 9:13 am
Alright so here's the summary version. I'm looking into substantially improving how I apply TDD/BDD to Zend Framework applications. At the moment my current PHPUnit test suites are frankly horrible. Luckily these are small projects.
The problem emerges in a few levels.
1. The Zend Framework has no built-in testing shortcuts
2. Test setup is inordinately long, even with some code refactoring to a Test subclass
3. PHPUnit doesn't have Mock Object support at a sufficient level (so stubs/mocks are hand coded more often than not)
4. It's a bit hard to mock objects directly instantiated in a ZF Controller
5. The suites are really a soup of integration tests.
My question here is to request opinions on how best to solve no. 4 (more a programming problem, than a strict testing one). I have been considering using something akin to a Registry which is handled independently of the Zend Framework. Instead of storing objects, it would store or locate/instantiate classes. As a quickie example of this thought, consider a theoretical PHPSpec example with an even more theoretical Zend Framework integration plugin:
Finally, a possible implementation ignoring all security concerns! We'll assume Article is a simple Model class. We'll also assume that PHPSpec Plugin has disabled auto-rendering of a View other than that we explicitly assign to the Response object.
Not sure what to go with here for substituting Models. Suggestions?
The problem emerges in a few levels.
1. The Zend Framework has no built-in testing shortcuts
2. Test setup is inordinately long, even with some code refactoring to a Test subclass
3. PHPUnit doesn't have Mock Object support at a sufficient level (so stubs/mocks are hand coded more often than not)
4. It's a bit hard to mock objects directly instantiated in a ZF Controller
5. The suites are really a soup of integration tests.
My question here is to request opinions on how best to solve no. 4 (more a programming problem, than a strict testing one). I have been considering using something akin to a Registry which is handled independently of the Zend Framework. Instead of storing objects, it would store or locate/instantiate classes. As a quickie example of this thought, consider a theoretical PHPSpec example with an even more theoretical Zend Framework integration plugin:
Code: Select all
<?php
// __ControllerName convention
class Describe__IndexController
{
/**
* In the opening setup, I create a Mocked class for a real
* Article model class. replaceModel() is a plugin method attached
* to a PHPSpec_Plugin_ZendFramework class which interfaces to a
* framework specific Zend_Action_Controller_Helper_ModelRegistry
* class which is responsible for loading/managing Model objects.
* Probably more accurately a Service Locator.
*/
public function before()
{
$this->article = phpmock('Article');
$this->replaceModel('Article', $this->article);
}
/**
* The replaced Article object is a Mock. Interaction with it within
* the targeted controller should meet the specificed expectations.
* post() is another Plugin method which dispatches a POST request to
* the selected Controller action (the first parameter, create).
*
* For completeness sake, it's assumed there is a Response object
* available, but it is also assumed a View is not rendered unless the
* before() setup method configured to allow View rendering into a
* Response. Don't worry about this detail...;)
*/
public function itShouldSaveNewArticleFromPostRequestToCreateAction()
{
$this->article->shouldSet('title')->with('I Am Article Title');
$this->article->shouldReceive('save')->once();
$this->post('create', array('title'=>'I Am Article Title'));
// because PHPSpec does not yet auto-validate PHPMocks
$this->spec($this->article->verify())->should->beTrue();
// Response expectations if warranted
$this->response()->should->beSuccess(); // i.e. status 200
}
} Code: Select all
class IndexController extends Zend_Controller_Action
{
public function createAction()
{
$article = $this->loadModel('Article');
$article->title = $_POST['title'];
$article->save();
$this->getResponse()->setBody('Article Saved');
}
}