Monday, June 30. 2008Testing Zend Framework MVC ApplicationsSince I originally started hacking on the Zend Framework MVC in the fall of 2006, I've been touting the fact that you can test ZF MVC projects by utilizing the Request and Response objects; indeed, this is what I actually did to test the Front Controller and Dispatcher. However, until recently, there was never an easy way to do so in your userland projects; the default request and response objects make it difficult to easily and quickly setup tests, and the methods introduced into the front controller to make it testable are largely undocumented. So, one of my ongoing projects the past few months has been to create an infrastructure for functional testing of ZF projects using PHPUnit. This past weekend, I made the final commits that make this functionality feature complete. The new functionality provides several facets:
What might you want to test?
The aim is to make testing your controllers trivial and fun. Let's look at an example: class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { public function setUp() { $this->bootstrap = array($this, 'appBootstrap'); parent::setUp(); } public function appBootstrap() { $this->frontController->registerPlugin( new Bugapp_Plugin_Initialize('test') ); } public function testCallingControllerWithoutActionShouldPullFromIndexAction() { $this->dispatch('/user'); $this->assertResponseCode(200); $this->assertController('user'); $this->assertAction('index'); } public function testIndexActionShouldContainLoginForm() { $this->dispatch('/user'); $this->assertResponseCode(200); $this->assertSelect('form#login'); } public function testValidLoginShouldInitializeAuthSessionAndRedirectToProfilePage() { $this->request ->setMethod('POST') ->setPost(array( 'username' => 'foobar', 'password' => 'foobar' )); $this->dispatch('/user/login'); $this->assertTrue(Zend_Auth::getInstance()->hasIdentity()); $this->assertRedirectTo('/user/view'); } }
You'll note that the I then have a few test cases. The first checks to ensure that the default action is called when no action is provided. The second checks to ensure that the login form is present on that page (by using a CSS selector to find a form with the id of 'login'). The third checks to see if I get a valid authentication session when logging in with good credentials, and that I get redirected to the appropriate location. This is, of course, just the tip of the iceberg; I've created a couple dozen other assertions as well. You can preview the functionality in the Zend Framework standard incubator; look for Zend_Test_PHPUnit_ControllerTestCase in there, as well as the Zend_Test documentation in the documentation tree (in human-readable DocBook XML). For those of you who decide to start playing with this, I'd love any feedback I can get. The best place to do so, however, is on the fw-mvc mailing list; instructions are on the ZF wiki. Comments
Display comments as
(Linear | Threaded)
This looks awesome. I've been waiting for some really solid testing classes for the controller level and this looks like it'll be a perfect fit!
Whoa!
I've been struggling since the beginning with unit testing my ZF MVC applications and this looks promising enough to take a deep dive and see how it can help me perform some TDD. I didn't notice you worked on this project Matthew, thanks! This looks really cool! In addition to the new testing facilities, Zend_Dom_Query sounds like it will be really helpful to me in another project I'm working on.
I hate to split hairs, but you said, "Mock test case classes for the HTTP versions of our Request and Response objects..." I think you meant stubs, not mocks: http://martinfowler.com/articles/mocksArentStubs.html http://www.phpunit.de/pocket_guide/3.0/en/mock-objects.html If, indeed, you really meant mocks, then feel free to put me in my place Indeed, I meant stubs, and I've updated it to reflect that. Thanks for the wrist slap!
Looks quite good. This is just in time as I had actually been looking into ways to test ZF apps =)
Great! Have been looking forward to this!
Thanks! This looks cool!
but ... I test this in 1.5.2. But lots of error happen. Need I update to 1.6 ? in 1.5.2, How can I test Controller ? My Error: class Rollenc_IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { public function setUp() { $this->bootstrap = array($this, 'appBootstrap'); parent::setUp(); } public function appBootstrap() { } public function testIndexAction() { $this->dispatch('/'); $this->assertResponseCode(200); $this->assertController('user'); $this->assertAction('index'); } } Zend_Controller_Exception: No default module defined for this application /usr/share/php/Zend/Controller/Dispatcher/Standard.php:211 /usr/share/php/Zend/Controller/Dispatcher/Standard.php:245 /usr/share/php/Zend/Controller/Front.php:914 /var/www/rollenc/tests/Zend/Test/PHPUnit/ControllerTestCase.php:158 /var/www/rollenc/tests/Rollenc/IndexControllerTest.php:18 /var/www/rollenc/tests/Rollenc/AllTests.php:44 /var/www/rollenc/tests/Rollenc/AllTests.php:60 What's the wrong? Your bootstrap needs to minimally set the controller directory -- do a call to $this->frontController->addControllerDirectory(...) in your appBootstrap() method. I didn't in my example, as my Initialization plugin does that sort of thing for me.
I apologize in advance for my n00bness, but can you post your Bugapp_Plugin_Initialize plugin for reference?
Thanks for your this quick reply.
It is right for me. Another problem: I found there is class Zend_Session and other classes in Zend Framework standard incubator, Their name is as the same as ZF core. Which is needed ? If i include incubator class first, I will got nothing when I insert data to DB with mysqli adapter The Zend_Session included in the incubator has some modifications that make it possible to test against it, though it is 100% BC with the version in trunk.
I myself have tested an app that included DB operations, and all worked correctly; however I was using the SQLite adapter. I see that there are local versions of the PDO adapter in the incubator, and perhaps this is causing the issue you are seeing. This component will be ready for trunk shortly, and when it is, you should not see such issues. I see.
Zend_Db_Table_Abstract is changed in the incubator. it need a preInsert notify, otherwise, return false before insert. All the things are good after replacing Zend_Db_Table_Abstract file into ZF core. There's any way to test, what objects the controllers returned, not just the HTML?
If i got an project controller, my index action will list my (5) projects. Theres anyway to test like this: $this->dispatch('/project'); $this->assert(5,$this->response->get('projects')); Regards How are your projects listed in the page? I'm assuming that you're generating HTML, and that there's a list. If so, you can use assertQueryContentCount() to perform such tasks: $this->assertQueryContentCount('#someId li', 5)
Actually its just a hypothetical case.
I've been using TDD since 2005, in Java and Ruby we can test our controllers without a view. I think thats a better way, since my controllers tests don't need to know nothing about my view. My controller test must assure theres 5 projects, how they'll be rendered, its a view problem. Thanks for your fast reply Best regards, nice, i got it
I just created a class, which extends Zend_View, overrided the "__set" method and now i store the objects on this new fake view class. On my TestInitializer (extends Zend_Controller_Plugin_Abstract), __construct method i added few lines too: $view = new TestView(); $this->_front->setParam('view',$view); $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view); Zend_Controller_Action_HelperBroker::addHelper($viewRenderer); So i can test my controllers without rendering the view, like this: $view = $this->_frontController->getParam('view'); $this->assertEquals(5, count($view->get('projects'))); Now i'll just refactor a little to make it more easy to read the tests I found something wrong with Zend_Test, isn't is right. I don't know, I think that I must tell you that in setMethod('GET') and setQuery then dispatch($url), something wrong happened.
array $_GET is empty. I send a mail to you, I maybe has resolved it and hoped that it could help us more happiness Yes, received your mail. This was reported on the issue tracker yesterday, and I was finally able to verify it this morning. It is definitely an issue with how $_GET is populated from the query string, and your patch should help resolve the issue.
A couple of questions:
1. How is it possible in the Zend framework to unit test the functions in my controller (that extends the ZendControllerAction class) without having to go through bootstrapping the zend framework etc. Currently we're overriding the constructor and using a parameter to indicate if its being run as part of a test but this isn't really elegant and sustainable. 2. Obviously using the Zend_Test_PHPUnit_ControllerTestCase class is the standard answer to question 1, however we're using the PHPUnit_Extensions_Story_TestCase for some BDD goodness which rules out using this and this class still doesn't allow the functions in the controller to be unit tested in isolation from the framework. Any help is most welcome. Rgds rob Zend_Controller_Action takes three parameters: a request object, a response object, and an array of invocation parameters (usually these are passed in from the front controller). So, to unit test without using Zend_Test_PHPUnit, simply make sure you instantiate your controller with the proper objects.
You'll also have a few other concerns: making sure the HelperBroker is setup correctly (i.e., the expected helpers are either present in the broker prior to instantiating the controller, and that the expected helper paths are set). The key one to ensure is present is the ViewRenderer. But that's all there is to it. Thanks for the quick response
I'm assuming that there is no way to test the code in my controllers without running some part of the Zend framework? If it requires setting up the framework it's integration testing...not unit testing which is what I'm interested in when doing TDD. You're *already* running part of Zend Framework if you're using Zend_Controller_Action. I'm really not sure what you're driving at.
As for unit versus functional testing, the approach you describe -- of instantiating the controller and testing individual methods -- is definitely unit testing. The other classes I mention -- the helper broker, the request/response objects, view object, etc. -- are part of your testing environment -- think of them as the scaffolding for your test. They can certainly be mocked or stubbed, but they are still necessary in order to execute the test. would you please tell me how can i run a test ?!
should i point a url in browser ? or it should be run in zend studio ?! how can i run the file ?! You'll need PHPUnit, and once you have that, simply 'phpunit UserControllerTest' should run the test (on the CLI). But you'll need to know a bit about PHPUnit to make sure your infrastructure is setup correctly. I'll be blogging on that in coming weeks.
Thx.So there is no way like the one in zend studio which you click the run button and go for run as PHPUnit text ?!
There is no other way of doing so without using CLI ?! Yes, of course you can run these test cases within Zend Studio -- they're valid PHPUnit test cases.
I just usually run them from the CLI. This allows running on a cronjob, with a CI server, or as a post-commit hook with your svn repository. Hi,
I'm making use of a custom inherited repsonse and request by request = new Herod_Controller_Request_Http($this->config); $response = new Herod_Controller_Response_Http($this->config); $this->frontController->setRequest($request); $this->frontController->setResponse($response); I tried to also do this in my appBootstrap within my ControllerTest-class. By running the test I get bugged by "Fatal error: Call to undefined method Zend_Controller_Response_HttpTestCase::addResponseJSCall() in /Users/marcofrank/workspace_zse_6_1/weblibs_own/hf/herod_1/library/Herod/Controller/Plugin.php on line 72" The test never regards my custom response-class. Can somebody explain my how to advice the test to take another class like Zend_Controller_Response_HttpTestCase? I would like to inherit a custom version of that class that implements function that are contained in my regular abstraction of the response class. thx and greetings! marco Currently, custom request and response objects are not supported; the stub classes we created have additional functionality not in the standard interfaces. Our plan is to introduce "Testable" interfaces for both request and response objects in a coming release.
First of all this test class is great - makes testing so much easier than it would have been without it.
That being said - I'm having problems bootstrapping my test cases. I define several constants in the bootstrap file which cause errors when setUp() is run several times (once for each test method). What is the intended way of bootstrapping the application so that the bootstrap isn't run again each time another test method is evaluated? I dislike the idea of adding if(!defined('constant')) to each of the defines just because the tests have issues here. You may dislike conditional constants, but it's a really good mechanism. I do it on one line:
defined('CONSTANT_NAME') or define('CONSTANT_NAME', 'value'); which is somewhat perlish, but gets it across really well. It's good defensive programming when it comes to constants. The bootstrap code is run for every test. This is to ensure that you have a clean environment for each and every test case. In PHPUnit 3.3, each test can actually be run in its own PHP process if desired (takes more time and memory), which would eliminate such warnings as you are seeing. Thanks for the answer, I suspected this might be the case.
Running each of the tests in its own process might be an option while the number of test cases is small, but as they crop up the performance hit may well be too heavy. I like your perlish way of doing the ifndef, by they way, lot cleaner than the full fledged if! clause. Add Comment
|
Calendar
QuicksearchLinks
CategoriesSyndicate This BlogShow tagged entries |
|||||||||||||||||||||||||||||||||||||||||||||||||





Testing Zend Framework MVC Applications - phly, boy, phly
Tracked: Jul 01, 11:14
Following last month's article by Ian, here's some thoughts on how to test a Zend Framework application. One of the unit testing best practices suggests to break dependencies, so you can test each component separately. The first problem that arises
Tracked: Aug 29, 09:20