Thursday, September 30, 2010

Symfony2 Controller Testing

Having a rich background in different MVC frameworks, one thing that was always unclear was how to test controllers. I think the main reason it wasn’t obvious is because controllers had always been sort of a black magic element in frameworks. There were too many conventions on where on the file system controller should be located, what dependencies it should have knowledge of and those dependencies were always wired with the controller the hard way (view layer).


Environment like that assumes no easy way for controller testing, since you can’t instantiate a controller and some of its primary dependencies to test the interaction - you have to boot the whole framework and write functional tests.


Because of how complicated that process is, people usually don’t unit-test controllers, functional test is the maximum you can get, but usually you don’t get anything at all.


Symfony2 changes that completely.


Initially Symfony2 framework had only conventional controller loading approach. Controller instance still was very lightweight and was not required to extend some parent class in order to work. If your controllers implemented ContainerAware interface, you would have the DIC (dependency injection container) inserted using ContainerAware::setContainer() method, which you could then use to access any service that you registered in DIC.


The proposed method of testing controllers at the time was a black box testing approach, where you test full requests to your application and assert their output like so:


Although this method is easy to read and understand, there are a couple of drawbacks:


  • To execute the test, we need to bootstrap the Kernel

  • This test can only assert response body, which makes it fragile to design changes

  • As a result of all of the above, it runs much slower and does much more than it needs to

In ideal world, I would want to test controller interaction with other services in my application, like so:


NOTE: Controller is just a POPO (plain old PHP object) without any base class that it needs to extend. Symfony2 doesn’t need anything but the class itself for controller to work.


NOTE: read more about mock object framework in PHPUnit


Well, the good news is that Symfony2 allows that. Now all your controllers can be services. The old, conventional approach is still supported and is irreplaceable for small application controllers, with no need for unit-testing.


To make the above example controller correctly interact with Symfony2 and work as expected, we need the following.


Create controller class:


Create DIC configuration, using the following xml:


Create routing configuration:


NOTE: in the above example, we used service_id:action instead of the regular BundleBundle:Controller:action (without the ‘Action’ suffix)


After all of the above is done, we need to inform Symfony2 of our services. To avoid creating a new Dependency Injection extension and creating configuration file entry, we can register our services directly:


NOTE: the above technique was originally invented by Kris Wallsmith in a project we were working on at OpenSky.


Now you’re ready to go. You need to include your bundle level routing file in the application level routing configuration, create the Index directory. The final file structure should look something like this:


After all the above steps are completed, you can try it from the browser, by going to:


    http://your_application/your_front_controller.php/index


Happy Coding!

Wednesday, September 29, 2010

Clean code talk on Friday

Clean code talk on Friday

Last Friday I gave a talk on unit-testing and programming best practices at OpenSky. This is the talk summary and some insight on how that turned out.

Tuesday, September 28, 2010

My review of Symfony NYC Meetup on September 23rd, 2010

My review of Symfony NYC Meetup on September 23rd, 2010

Some of you might, others might not know that our company, OpenSky, became the official sponsor of Symfony NYC Meetups, which means, that we’ll be seeing you the last Thursday of every month, here, at OpenSky HQ in NYC for the foreseeable future…