7

I've got a form which isn't connected to a entity but does have constraints like mentioned in here: http://symfony.com/doc/current/book/forms.html#adding-validation

ContactBundle\Tests\Form\Type\TestedTypeTest::testSubmitValidData Symfony\Component\OptionsResolver\Exception\InvalidOptionsException: The option "constraints" does not exist. Known options are: "action", "attr", "auto_initialize", "block_name", "by_reference", "compound", "data", "data_class", "disabled", "empty_data", "error_bubbling", "inherit_data", "label", "label_attr", "mapped", "max_length", "method", "pattern", "post_max_size_message", "property_path", "read_only", "required", "translation_domain", "trim", "virtual"

Here is part of the form:

class ContactType extends AbstractType
{

/**
 * Build the form
 * @param \Symfony\Component\Form\FormBuilderInterface $builder BuilderInterface
 * @param array $aOption Array of options
 */
public function buildForm(FormBuilderInterface $builder, array $aOption)
{
    ///..
    $builder->add('name', 'text', array(
                'constraints' => array(
                    new NotBlank(),
                    new Length(array('min' => 3)),
                ),
            ))
            ->add('address', 'textarea', array(
                'required' => false
            ))
            //..
            ;
    //..

Here is the unit test

class TestedTypeTest extends TypeTestCase
{

public function testSubmitValidData()
{
    $formData = array(
        'name' => 'Test Name',
        'address' => '',
    );
    $type = new ContactType();
    $form = $this->factory->create($type, $formData);
    $form->submit($formData);

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

    $view = $form->createView();
    $children = $view->children;

    foreach (array_keys($formData) as $key) {
        $this->assertArrayHasKey($key, $children);
    }
}
}

I'm guessing that this is a problem with the test instead of the form as the form works as expected. I'm not sure what I should change. Any help or advice would be handy

Thanks

4 Answers 4

6

I think the problem is that you also pass the formData to the factory's create method.

It should be fine to just pass the form data via $form->submit($formData) like you already have in your code

More docs: http://symfony.com/doc/current/cookbook/form/unit_testing.html

EDIT:

Well it seems you have to add the validator/constaint extensions to the factory object in your test setup like stated here http://symfony.com/doc/current/cookbook/form/unit_testing.html#adding-custom-extensions

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

5 Comments

Yeah I did try that: $type = new ContactType(); $form = $this->factory->create($type); but its still the same issue. According to the docs the second param of create could be a object, array or nothing. public function create($type = 'form', $data = null, array $options = array()); Api docs for FormFactoryInterface: api.symfony.com/2.3/Symfony/Component/Form/…
Which line is causing the execption? (And the other arguments of create() are finally passed as second parameter in your FormType's buildForm(FormBuilderInterface $builder, array $aOption))
From the stack trace: symfony/symfony/src/Symfony/Component/OptionsResolver/OptionsResolver.php:255 symfony/symfony/src/Symfony/Component/OptionsResolver/OptionsResolver.php:219 symfony/symfony/src/Symfony/Component/Form/ResolvedFormType.php:109 symfony/symfony/src/Symfony/Component/Form/FormFactory.php:87 symfony/symfony/src/Symfony/Component/Form/FormBuilder.php:106 symfony/symfony/src/Symfony/Component/Form/FormBuilder.php:268 symfony/symfony/src/Symfony/Component/Form/FormBuilder.php:216 symfony/symfony/src/Symfony/Component/Form/FormFactory.php:39 /Tests/Form/Type/ContactTypeTest.php:34
Sorry its: $form = $this->factory->create($type);
I updated my answer... you have to add some setup stuff to get constraints supported by the test framework...
5

Yes Rob was right I needed to add validator/constraint extensions. Like so:

 class TestedTypeTest extends TypeTestCase
 {

  protected function setUp()
  {
    parent::setUp();

    $validator = $this->getMock('\Symfony\Component\Validator\Validator\ValidatorInterface');
    $validator->method('validate')->will($this->returnValue(new ConstraintViolationList()));
    $formTypeExtension = new FormTypeValidatorExtension($validator);
    $coreExtension = new CoreExtension();

    $this->factory = Forms::createFormFactoryBuilder()
            ->addExtensions($this->getExtensions())
            ->addExtension($coreExtension)
            ->addTypeExtension($formTypeExtension)
            ->getFormFactory();
}
//..

3 Comments

Doesn't this just stub out the constraints? So it's hard to test if the data submitted passes or fails the constraints?
Indeed, it looks like Victor S is correct. I will allow you to complete the test, but it will not test the constraints. I created a testSubmitInvalidData method with invalid input, but $form->isValid() will always return true. Close, but no cigar.
Your ConstraintViolationList is empty, your form is always valid
4

I suggest you to override the TypeTestCase::getExtensions() method

use Symfony\Component\Form\Extension\Core\CoreExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Form;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextFactory;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Validator\RecursiveValidator;

....

   public function getExtensions()
    {
        $extensions = parent::getExtensions();
        $metadataFactory = new FakeMetadataFactory();
        $metadataFactory->addMetadata(new ClassMetadata(  Form::class));
        $validator = $this->createValidator($metadataFactory);

        $extensions[] = new CoreExtension();
        $extensions[] = new ValidatorExtension($validator);

        return $extensions;
    }

add the method createValidator() in your test class :

    protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
    {
        $translator = new IdentityTranslator();
        $translator->setLocale('en');
        $contextFactory = new ExecutionContextFactory($translator);
        $validatorFactory = new ConstraintValidatorFactory();
        return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $objectInitializers);
    }

No need to override TypeTestCase::getTypeExtensions() because ValidatorExtension class already has a loadTypeExtensions() method.

Then, your test method can be like this :

public function testSubmitValidData()
{
    $formData = array(
        'name' => 'Test Name',
        'address' => '',
    );
    $form = $this->factory->create(ContactType::class);
    $form->submit($formData);
    $this->assertTrue($form->isValid());
    $this->assertTrue($form->isSynchronized());

    $view = $form->createView();
    $children = $view->children;

    foreach (array_keys($formData) as $key) {
        $this->assertArrayHasKey($key, $children);
    }
}

You can test invalid data :

public function testSubmitInvalidValidData()
{
    $formData = array(
        'name' => 'T',
    );
    $form = $this->factory->create(ContactType::class);
    $form->submit($formData);
    $this->assertFalse($form->isValid());
    $this->assertFalse($view->children['name']->vars['valid']);

}

sources :

Comments

3

This problem can still occurs with Symfony 4.4 but it is easy to solve.

The ValidatorExtensionTrait must be added to the class that runs the test.

use Symfony\Component\Form\Test\TypeTestCase;

class TestedTypeTest extends TypeTestCase
{
    use \Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;

    public function testSubmitValidData()
    {
        $formData = [...];
        $type = new ContactType();
        $form = $this->factory->create($type, $formData);
    }
}

The trait takes care of adding the extension required to make the "constraints" option available.

Comments

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.