Symfony2: Testing Your Controllers

Apparently not everyone agrees on how to unit test their Symfony2 controllers. Some treat controller code as the application’s “glue”: a controller does the real job of transforming a request to a response. Thus it should be tested by making a request and check the received response for the right contents. Others treat controller code just like any other code – which means that every path the interpreter may take, should be tested.

The first kind of testing is called functional testing and can be done by extending the WebTestCase class, creating a client, making a request and crawling the response using CSS selectors. It is indeed very important to test your application in this way – it makes sure all other “glue-like” parts of the system are also in place (like service definitions, configuration parameters, etc.). But it is very, very inefficient. You shouldn’t use it to test all possible request-response pairs.

Unit testing your controllers – in my opinion – should be done in the ordinary way, by creating a PHPUnit testcase, instantiating the controller, injecting the necessary dependencies (not the real ones, but stubs/mocks), and finally making some assertions. The easiest way to accomplish this, is by defining the controller as a service, and use setters to provide the controller with it’s dependencies.

Creating the sample controller

The controller we will use in this example is a rather simple RegistrationController:

namespace Matthias\RegistrationBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\FormFactoryInterface;
use Matthias\RegistrationBundle\Form\Type\RegistrationType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;

/**
 * @Route(service = "matthias_registration.registration_controller")
 */
class RegistrationController
{
    /** @var \Swift_Mailer */
    private $mailer;

    /** @var FormFactoryInterface */
    private $formFactory;

    /** @var EngineInterface */
    private $templating;

    /**
     * @Route("/register", name = "register")
     * @Method({ "GET", "POST"})
     */
    public function registerAction(Request $request)
    {
        $form = $this->formFactory->create(new RegistrationType);

        if ('POST' === $request->getMethod()) {
            $form->bindRequest($request);
            if ($form->isValid()) {
                $message = \Swift_Message::newInstance('Registration', 'Confirm...');

                $this->mailer->send($message);

                return new RedirectResponse('/');
            }
        }

        return new Response($this->templating->render(
            'MatthiasRegistrationBundle:Registration:registration.html.twig', array(
                'form' => $form->createView(),
            )
        ));
    }

    public function setMailer(\Swift_Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function setFormFactory(FormFactoryInterface $formFactory)
    {
        $this->formFactory = $formFactory;
    }

    public function setTemplating($templating)
    {
        $this->templating = $templating;
    }
}

As you can see, the service needs the form factory, the templating engine and the mailer. These dependencies should be injected by adding multiple method calls to the service definition:

<service id="matthias_registration.registration_controller" class="Matthias\RegistrationBundle\Controller\RegistrationController">
    <call method="setFormFactory">
        <argument type="service" id="form.factory" />
    </call>
    <call method="setTemplating">
        <argument type="service" id="templating" />
    </call>
    <call method="setMailer">
        <argument type="service" id="mailer" />
    </call>
</service>

Testing the controller

Now, we don’t need to extend from the WebTestCase to test this controller. We only need to provide it with the necessary dependencies. The code below demonstrates how to unit test the default flow: create a form, create a view for it, and let the templating engine render it.

use Matthias\RegistrationBundle\Controller\RegistrationController;
use Symfony\Component\HttpFoundation\Request;

class RegistrationControllerTest extends \PHPUnit_Framework_TestCase
{
    public function testRegistrationWithoutPostMethodRendersForm()
    {
        $controller = new RegistrationController;

        $form = $this
            ->getMockBuilder('Symfony\Tests\Component\Form\FormInterface')
            ->setMethods(array('createView'))
            ->getMock()
        ;
        $form
            ->expects($this->once())
            ->method('createView')
        ;

        $formFactory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
        $formFactory
            ->expects($this->once())
            ->method('create')
            ->will($this->returnValue($form))
        ;

        $templating = $this->getMock('Symfony\Component\Templating\EngineInterface');
        $templating
            ->expects($this->once())
            ->method('render')
        ;

        $controller->setFormFactory($formFactory);
        $controller->setTemplating($templating);

        $controller->registerAction(new Request);
    }
}

A few mocks are created, disguised as the objects which the controller expects. The default flow only requires a form factory and a templating engine. The form factory should return a form. Some so-called expectations are added to the mock objects, to make sure that certain methods will be called exactly once.

Note that the test method does not contain any manual assertions. Nevertheless, the expections that were defined, are really assertions, so we have indirectly tested the way the action code interacts with other objects.

Choosing setter injection as the strategy for dependency management really pays off when you are testing more than one controller action, since not all actions will have the same dependencies. For example, when you test the registerAction again, to make sure a mail is sent, you only need the form factory and the mailer:

class RegistrationControllerTest extends \PHPUnit_Framework_TestCase
{
    // ...

    public function testRegistrationWithPostMethodValidatesFormAndSendsMailWhenValid()
    {
        $controller = new RegistrationController;

        $request = new Request();
        $request->setMethod('POST');

        $form = $this
            ->getMockBuilder('Symfony\Tests\Component\Form\FormInterface')
            ->setMethods(array('bindRequest', 'isValid'))
            ->getMock()
        ;
        $form
            ->expects($this->once())
            ->method('bindRequest')
            ->with($this->equalTo($request))
        ;
        $form
            ->expects($this->once())
            ->method('isValid')
            ->will($this->returnValue(true))
        ;

        $formFactory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
        $formFactory
            ->expects($this->once())
            ->method('create')
            ->will($this->returnValue($form))
        ;

        $mailer = $this
            ->getMockBuilder('\Swift_Mailer')
            ->disableOriginalConstructor()
            ->getMock()
        ;
        $mailer
            ->expects($this->once())
            ->method('send')
        ;

        $controller->setFormFactory($formFactory);
        $controller->setMailer($mailer);

        $controller->registerAction($request);
    }
}

See also…

Before making this way of testing part of your daily routine, you might want to read up on using stubs (which are stand-in objects that return a controlled value) and mocks (which are used to check that certain methods are called the desired number of times). See the chapter Test doubles in the PHPUnit documentation. It really pays off!

Posted in Controller, PHPUnit, Symfony2, Testing | 16 Comments

16 Responses to Symfony2: Testing Your Controllers

  • # June 11, 2012 at 21:48

    What is the benefit of this unit test over a functional test? The functional test is obviously going to be slower. But it will also test the configuration and availability of services. It will also catch any issue your unit test will cover. And I bet if there is an issue in the logic you would find it just as quickly with the functional as with the unit test. But if you change any of your services you will need to rewrite 10-20 LOC of mock configs. Not the kind of thing that makes we want to refactor, which is what unit test should in theory make easier .. not harder.

    Lukas

    Reply

    • # June 14, 2012 at 03:44

      It will also catch any issue your unit test will cover
      There are cases where I don’t think this is true, especially in the area of exception handling. Consider the case of confirming a registration, from your controller you might call $confirmationService->confirm(‘email@example.org’). This application service might have several exceptions – EmailNotRegisteredException, EmailAlreadyConfirmedException, EmailBannedException, etc that all simply pass a different error message to your template. A thorough functional test suite of such a controller might take 10 seconds whereas the unit equivalent with maybe one functional test would be a couple of seconds at most. There’s also the problem of how to fake service responses, e.g. in the blog post example you might want to write a handler for exceptions during sending – easily fakable in unit tests but not as easy in functional.

      But if you change any of your services you will need to rewrite 10-20 LOC of mock configs
      That’s a possible problem for any unit tests – how to effectively setup your stubs/mocks. I don’t think Controllers are special in this regard – they are still just a class taking an input, interacting with services and generating an output. I find PHPUnit’s mocks especially bad for mock config because it combines the Arrange and Assert steps. I’m a big fan of Phake in this regard: https://github.com/mlively/Phake

      Daniel Holmes

      Reply

      • # June 14, 2012 at 09:26

        Hi Lukas and Daniel,
        I was hoping for some strong advise on this matter – thank you for providing it! Testing a controller with unit tests allows you much more fine-grained tests. This way you may cover all execution paths of the code. Then, using functional tests, you may (and should) test the flow which is most common for your users, to verify that they (real persons or webservice clients) are able to interact with your application.
        Thanks for your suggestion about Phake, Daniel, I will give it a try.

        Matthias Noback

        Reply

  • # July 22, 2012 at 09:33

    I always test my controllers via functional tests, and test the services with individual unit tests. Another great mock / stub framework is Mockery. https://github.com/padraic/mockery

    ezonno

    Reply

  • # January 7, 2013 at 18:02

    Hello there,
    Thank you for the illustration.
    We are currently unit-testing controller on our Sf2 app, but we get some blockers because the most of the controllers use several services, and it is so hard to mock them. So, how would you suggest to proceed here?, We’ve researched over the web but all of them advise to use functional test, so the services initialization is managed by the framework itself.
    Thanks in advance

    Gustavo

    Reply

    • # January 7, 2013 at 22:54

      Hi Gustavo,

      After some discussion (see above) I have decided that testing the controller “stand-alone” is not the way. You should have all clean unit-testable code in some other place. The controller should only hook things up. This integration of components should be tested using a functional test.
      (In my opinion that is ;) )

      Best regards,

      Matthias

      Matthias Noback

      Reply

      • # June 27, 2013 at 13:21

        The challenge is that Symfony makes it really hard for non-Controllers to access everything it has set up, without lots of dependency injection “wiring” all over the place.

        So Controllers end up with lots of non-controller logic.

        James R Grinter

        Reply

        • # July 1, 2013 at 09:17

          A nice alternative: http://www.whitewashing.de/2013/06/27/extending_symfony2__controller_utilities.html
          Symfony actually makes it very easy to access everything – dependency wiring is what developing Symfony applications is all about. When using too many services inside one controller, maybe split the controller into multiple controllers (chances are you are giving it too many responsibilities), or combine services in related sets of services (like in the article I mentioned above).

          Matthias Noback

          Reply

  • # May 29, 2013 at 10:44

    IMO, fine-grained controller testing has no sense.

    Controllers should be slim and have NO BUSINESS LOGIC at all. All the fine-grained testing should be derived to model classes.

    If you want to verifiy that the method ‘bind’ is being called, go on, but these are tests that provide questionable value.

    Test user interaction with functional tests, and bussines logic with unitary tests.

    Carles Climent

    Reply

    • # May 29, 2013 at 10:46

      Sorry, didn’t read all the comments.

      Nice to see you arrived to the same conclusion ;)

      Carles Climent

      Reply

  • # July 12, 2013 at 07:51

    Try Goutte & Crawler & CSS Selector & Webcase please ;)

    Boyer

    Reply

    • # July 16, 2013 at 08:48

      Right! As you can read in the comments I already changed my mind.

      Matthias Noback

      Reply

  • # October 17, 2013 at 13:30

    I had a similiar problem, when I try to spec my controllers with phpspec 2.x, and it works, I mean I could use stubs and mocks for form, router, session, any other colaborators for my controller but thing stop when in my controller I should test/spec security, simply get security.context doesn’t work because it’s has all fields null. So because of this my spec for the controller now it breaks.
    I google it about test and symfony2 and I get over this presentation from slideshare, by Fabien: http://www.slideshare.net/fabpot/unit-and-functional-testing-with-symfony2 where at 16 it says: ” Do not write Unit Test for a Controller”.

    Silviu

    Reply

    • # October 18, 2013 at 15:41

      Right, as you can see in the other comments, I’ve changed my mind about this too. Controllers should be really thin, and you can test its behavior better using a functional or acceptance test.

      Matthias Noback

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

* *

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>