3

I have a form which looks like this, when it is rendered:

<form name="service_user_registration" method="post" action="" novalidate="novalidate">

        <div>
             <label for="service_user_registration_user_email" class="required">E-mail</label>
             <input type="email" id="service_user_registration_user_email" name="service_user_registration[user][email]" required="required" size="30" class="sample_class" title="Sample title" placeholder="Your e-mail..." />
        </div>

        <div>
             <label for="service_user_registration_account_name" class="required">Company name</label>
             <input type="text" id="service_user_registration_account_name" name="service_user_registration[account][name]" required="required" placeholder="Your company name..." />
        </div>

        <button type="submit" id="service_user_registration_register" name="service_user_registration[register]" class="btn btn-primary">Register</button>

    <input type="hidden" id="service_user_registration__token" name="service_user_registration[_token]" value="VzkWJQw-2p-1lfBJoT5xwNB4dR2reT47i0ZTZES9LqY" />
</form>

I want to unit test the form submission, if the data that I enter is written in the underlying object, which is a Registration object. I do it like below, but I do not know how to set the $data array properly, because name attribute of fields appears to be like an array.

class RegistrationTypeTest extends TypeTestCase
{
    public function testSubmit()
    {
        $user = new User();
        $account = new Account();
        $registration = new Registration($user,$account);

        $form = $this->factory->create(new RegistrationType(), $registration);

        $data = array(
            'service_user_registration[user][email]' => '[email protected]',
            'service_user_registration[account][name]' => 'New company'
        );

        $form->submit($data);

        $this->assertTrue($form->isSynchronized());

        $formData = $form->getData();

        $this->assertInstanceOf('Service\Bundle\UserBundle\Entity\Registration', $formData);

        $this->assertEquals($data['service_user_registration[user][email]'],   $formData->getUser()->getEmail());
        $this->assertEquals($data['service_user_registration[account][name]'], $formData->getAccount()->getName());
    }
}

The last two asserts fail because the $formData in a Registration object, has everything null, which should be the case when the submitted fields are both blank. I have also written the $data like an array of arrays, but did not change anything.

Edit (added form types):

class RegistrationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('user', new RegistrationUserType())
                ->add('account', new RegistrationAccountType())
                ->add('register', 'submit');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        # Using cascade validation because Valid constraint can not have validation groups

        $resolver->setDefaults([
            'cascade_validation' => true,
            'intention' => $this->getName()
        ]);
    }

    public function getName()
    {
        return 'service_user_registration';
    }
}

class RegistrationUserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('email', 'email');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => array('registration'),
            'data_class' => 'Service\Bundle\UserBundle\Entity\User',
            'intention' => $this->getName()
        ));
    }

    public function getName()
    {
        return 'service_user_user_registration';
    }
}

class RegistrationAccountType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name', 'text');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => array('registration'),
            'data_class' => 'Service\Bundle\UserBundle\Entity\Account',
            'intention' => $this->getName()
        ));
    }

    public function getName()
    {
        return 'service_user_account_registration';
    }
}
5
  • Could we have a look at your form-type? Commented May 10, 2014 at 11:26
  • Ofcourse, I have added the form types. All the code works perfectly, when testing manually. I only have a problem with unit test. Thank you for the effort. Commented May 10, 2014 at 11:31
  • You're not submitting a csrf-token alongside the data in your unit test. So I guess $form->isValid() returns false in your unit test? Your main problem is that both $formData->getUser()->getEmail() and $formData->getAccount()->getName() return null, right ? Commented May 10, 2014 at 11:34
  • $this->assertTrue($form->isValid()); // This is ok, it doesnt output an error. Yes, both last asserts return null. Commented May 10, 2014 at 11:38
  • I expressed myself wrong. Both asserts would say this: Failed asserting that null matches expected '[email protected]'. Commented May 10, 2014 at 11:45

1 Answer 1

3

And then people go on to whine that TDD is stupid, it's hard, it sucks, and, finally, it's dead...

Do yourself a favor and stop trying to unit test stuff you should not be unit testing in the first place. Write some higher level test. You have at least two options.

First, you could write integration tests specific to that form that test its behavior in the wider context. For example, you could create an instance of the form, pass data to it and then test that the model that you get out of the form has the data you passed.

Second, you could write end-to-end tests that involve this form. If the form doesn't work right, the tests will fail.

Since end-to-end tests are usually slower, you could actually write one or two of them to test that your code behaves correctly when the form is both valid and not valid. For instance, when the form is valid, a model is persisted and the user is redirected somewhere. If it's not, nothing gets persisted and the form is shown again with previously entered data and validation messages.

And then you could write several integration tests specific to the form to test some other combinations of data input to make sure it behaves correctly in those cases.

Actually, if you're testing validation, you could omit integration tests for the form itself and write tests to test validation rules right on the model. But you'd still need end-to-end tests to test the overall flow of the app.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer! I am actually trying do to an integration test specific to this form, as you say in the "First" option. Try to pass data to the form and see it if is gets into the model. Writing functional tests works so far, it's probably what you proposed in second option, right?
Well, yea, it's close. But TypeTestCase tests are still low level, because they're isolated from the container. For example, they're not loading any form extensions. So, I suggest running form integration tests with the container in place. Forget TypeTestCase — it was meant for testing reusable form types in isolation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.