modify form action with symfony2 and phpunit

I'm currently working with Symfony2 and I'm testing my project with PHPUnit. I want to test an exception when a form is submitted with the wrong parameters or the URL isn't complete.

I went trough the documentation of Symfony2 and PHPUnit but didn't find any class/method to do so.

How can I change the value of a form's action? I want to use PHPUnit so the report created is up to date and I can see the coverage of my code.

EDIT:

To clarify my question, some new content. How do I test the line starting with '>' in my controller? (throw $this->createNotFoundException('Unable to find ParkingZone entity.');)

When the user modifies the action link, in the controller the process will go trough the exception (or error message, if this action is chosen). How can I test this case?

Controller

/**
 * Edits an existing ParkingZone entity.
 *
 * @Route("/{id}/update", name="parkingzone_update")
 * @Method("post")
 * @Template("RatpGarageL1Bundle:ParkingZone:edit.html.twig")
 */
public function updateAction($id)
{
    $em = $this->getDoctrine()->getEntityManager();

    $entity = $em->getRepository('RatpGarageL1Bundle:ParkingZone')->find($id);

    if (!$entity) {
>        throw $this->createNotFoundException('Unable to find ParkingZone entity.');
    }

    $editForm   = $this->createForm(new ParkingZoneType(), $entity);
    $deleteForm = $this->createDeleteForm($id);

    $request = $this->getRequest();

    $editForm->bindRequest($request);

    if ($editForm->isValid()) {
        $em->persist($entity);
        $em->flush();

        return $this->redirect($this->generateUrl('parkingzone_edit', array('id' => $id)));
    }

    return array(
        'entity'      => $entity,
        'edit_form'   => $editForm->createView(),
        'delete_form' => $deleteForm->createView(),
    );
}

View:

<form action="{{ path('parkingzone_update', { 'id': entity.id }) }}" method="post" {{ form_enctype(form) }}>
    <div class="control-group">
        {{ form_label(form.name, 'Name', { 'attr': {'class': 'control-label'} } ) }}
        <div class="controls error">
            {{ form_widget(form.name, { 'attr': {'class': ''} } ) }}
            <span class="help-inline">{{ form_errors(form.name) }}</span>
        </div>
    </div>
    <div class="control-group">
        {{ form_label(form.orderSequence, 'Rang', { 'attr': {'class': 'control-label'} } ) }}
        <div class="controls error">
            {{ form_widget(form.orderSequence, { 'attr': {'class': ''} } ) }}
            <span class="help-inline">{{ form_errors(form.orderSequence) }}</span>
        </div>
    </div>
    <div class="control-group">
        {{ form_label(form.image, 'Image', { 'attr': {'class': 'control-label'} } ) }}
        <div class="controls error">
            {{ form_widget(form.image, { 'attr': {'class': ''} } ) }}
            <span class="help-inline">{{ form_errors(form.image) }}</span>
        </div>
    </div>
    {{ form_rest(form) }}
    <div class="form-actions">
        <button type="submit" class="btn btn-primary">Enregistrer</button>
        <a href="{{ path('parkingzone') }}" class="btn">Annuler</a>
    </div>
</form>

Answers


Symfony itself does not have any objects through which it is possible to manipulate the form's action as it is set in the html (twig files). However, twig provides the capability to dynamically change the form's action in the twig file.

The basic approach is for the controller to pass a parameter into the twig file via the render call. Then the twig file can use this parameter to set the form action dynamically. If the controller uses a session variable to determine the value of this parameter then by setting the value of this session variable in the test program it is possible to set the form action specifically for the test.

For example in the controller:

public function indexAction()
{
    $session = $this->get('session');
    $formAction = $session->get('formAction');
    if (empty($formAction)) $formAction = '/my/normal/route';

    ...

    return $this->render('MyBundle:XXX:index.html.twig', array(
        'form' =>  $form->createView(), 'formAction' => $formAction)
    );
}

And then, in the twig file:

<form id="myForm" name="myForm" action="{{ formAction }}" method="post">
...
</form>

And then, in the test program:

$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->set('formAction', '/test/route');
$session->save();

// do the test

This isn't the only way, there are various possibilities. For example, the session variable could be $testMode and if this variable is set the form passes $testMode = true into the render call. Then the twig file could set the form action to one of two values depending on the value of the testMode variable.


Symfony2 makes a distinction between unit testing of individual classes and functional testing of application behaviour. Unit testing is carried out by directly instantiating a class and calling methods on it. Functional testing is carried out by simulating requests and testing responses. See symfony testing for further detail.

Form submission can only be tested functionally as it is handled by a Symfony controller which always operates in the context of a container. Symfony functional tests must extend the WebTestCase class. This class provides access to a client which is used to request URLs, click links, select buttons and submit forms. These actions return a crawler instance representing the HTML response which is used to verify that the response contains the expected content.

It is only appropriate to test that exceptions are thrown when carrying out unit tests as functional tests cover interaction with the user. The user should never be aware that an exception has been thrown. Therefore the worst case scenario is that the exception is caught by Symfony and in production the user is presented with the catch-all response "Oops, an error has occurred" (or similar customised message). However, this should really only occur when the application is broken and not because the user has used the application incorrectly. Therefore it is not something that would typically be tested for in a functional test.

Regarding the first scenario mentioned in the question - submitting a form with the wrong parameters. In this case the user should be presented with an error message(s) telling them what was wrong with their input. Ideally the controller should not be throwing an exception but symfony validation should be used to automatically generate error messages next to each field as appropriate. Regardless of how the errors are displayed this can be tested by checking that the response to submitting the form contains the expected error(s). For example:

class MyControllerTest extends WebTestCase
{
    public function testCreateUserValidation()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/new-user');
        $form = $crawler->selectButton('submit')->form();
        $crawler = $client->submit($form, array('name' => '', 'email' => 'xxx'));
        $this->assertTrue($crawler->filter('html:contains("Name must not be blank")')->count() > 0,
                          "Page contains 'Name must not be blank'");
        $this->assertTrue($crawler->filter('html:contains("Invalid email address")')->count() > 0,
                          "Page contains 'Invalid email address'");
    }
}

Regarding the second scenario - where the URL isn't complete. With Symfony any URL which does not match a defined route will result in a NotFoundHttpException. In development this will result in a message such as 'No route found for "GET /xxx"'. In production it will result in the catch-all 'Oops, an error has occurred'. It would be possible to test in development that the response contains 'No route found'. However, in practice it doesn't really make sense to test this as it's handled by the Symfony framework and is therefore a given.

EDIT:

Regarding the scenario where the URL contains invalid data which identifies an object. This could be tested (in development) like this in the unit test program:

$client = static::createClient();

$page = $client->request('GET', '/update/XXX');
$exceptionThrown = ($page->filter('html:contains("NotFoundException")')->count() > 0) && ($page->filter('html:contains("Unable to find ParkingZone entity")')->count() > 0);
$this->assertTrue($exceptionThrown, "Exception thrown 'Unable to find ParkingZone entity'");

If you just want to test that an exception has been thrown rather than a specific type / message you can just filter the html for 'Exception'. Remember that in production the user will only see "Oops, an error has occurred", the word 'Exception' will not be present.


Thanks to @redbirdo with his last answer, I found a solution without messing out with the controllers. I only changed few lines in the templates.

ControllerTest

public function testUpdate()
{
    $client = static::createClient();
    $session = $client->getContainer()->get('session');
    $session->set('testActionForm', 'abc');
    $session->save();       // This line is important or you template won't see the variable

    // ... tests
}

View

{% if app.session.has('testActionForm') %}
    {% set actionForm = path('parkingzone_update', { 'id': app.session.get('testActionForm') }) %}
{% else %}
    {% set actionForm = path('parkingzone_update', { 'id': entity.id }) %}
{% endif %}

<form action="{{ actionForm }}" {{ form_enctype(form) }} method="POST" class="form-horizontal">
// ... rest of the form

Need Your Help

Complex views in MVC

model-view-controller

In the MVC model, where does the view differentiate between the model when the views are sufficiently complex?