【问题标题】:Unit testing Symfony forms with entities使用实体对 Symfony 表单进行单元测试
【发布时间】:2015-06-23 10:33:00
【问题描述】:

我无法对内部包含“实体”字段的 Symfony 表单进行单元测试。

我找到了潜在的解决方案 herehere,但是我无法让它们发挥作用。

这是我的代码:

FormsTest.php

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

    $this->factory = Forms::createFormFactoryBuilder()
        ->addExtensions($this->getExtensions())
        ->getFormFactory();
}

protected function getExtensions()
{
    $mockEntityType = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\Type\EntityType')
        ->disableOriginalConstructor()
        ->getMock();

    $mockEntityType->expects($this->any())->method('getName')
        ->will($this->returnValue('entity'));

    return array(new PreloadedExtension(array(
        $mockEntityType->getName() => $mockEntityType,
    ), array()));
}

public function testSubmitValidData()
{
    $formData = array(
        'name' => 'Mbalmayo',
        'latitude' => 3.5165475,
        'longitude' => 11.5144015,
        'zoomLevel' => 12.0,
        'region' => 'Centre',
    );

    $type = new CitiesType();
    $form = $this->factory->create($type, null);

    $object = new Cities();
    $object->fromArray($formData);

    // submit the data to the form directly
    $form->submit($formData);

    $this->assertTrue($form->isSynchronized());
    $this->assertEquals($object, $form->getData());

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

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

此代码基于我之前找到的解决方案。

CitiesType.php

/**
 * Builds the form data for the cities
 *
 * @param FormBuilderInterface  $builder The FormBuilderInterface to use
 * @param array                 $options The options for the form, if any
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        /* Several adds that are pointless for this problem */
        ->add('region', 'entity', array('class' => 'SmopaAgentFinderBundle:Regions',
                'property' => 'name',
                'required' => true,
                'label' => 'city.new.region',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('r')
                        ->orderBy('r.name', 'ASC');
                },
                'empty_value' => 'Select city\'s region',
                'attr' => array('class' => 'new_city_combo_box')
            )
        );
}

目前,我收到此错误:

1) FormsTest::testSubmitValidData Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException: 选项“attr”、“class”、“empty_value”、“label”、“property”、 “query_builder”、“必需”不存在。已知选项有:“”。

我需要对这些表格进行测试,但我完全没有想法。有什么帮助吗?

【问题讨论】:

标签: php forms unit-testing symfony


【解决方案1】:

发生这种情况是因为 OptionsResolver 不知道这些选项。您的 EntityType 模拟应该声明它们:

对于 Symfony 2.7:

// use Symfony\Component\OptionsResolver\OptionsResolver;

$mockEntityType->method('setDefaultOptions')->will(
    $this->returnCallback(
        function (OptionsResolver $resolver)
        {
            $resolver->setDefaults(
                array(
                    'choice_label' => null,
                    'class' => null,
                    'query_builder' => null,
                    'required' => null,
                )
            );
        }
    )
);

【讨论】:

  • 使用这个设置我得到一个类型错误:参数 1 传递给 Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() 必须实现接口 Doctrine\Common\Persistence\ManagerRegistry,没有给出,使用 Symfony 3.0.6 在第 85 行调用 /var/www/shn/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php 当然我将 setDefaultOptions 更改为 configureOptions
【解决方案2】:

'property' 选项从 symfony 2.7 开始被 'choice_label' 取代。

documentation

【讨论】:

    【解决方案3】:

    我想介绍我的解决方案。首先,我将实体类型注入到我的表单中。这是我在我的 UserBundle 中所做的,它扩展了 FOSUserBundle,因为我需要将组分配给用户:

    namespace UserBundle\Form\Type;
    
    
    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\Extension\Core\Type\EmailType;
    use Symfony\Component\Form\Extension\Core\Type\PasswordType;
    use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Validator\Constraints\Email;
    use UserBundle\Entity\Group;
    
    /**
     * Class UserType
     * @package UserBundle\Form\Type
     */
    class UserType extends AbstractType
    {
    
        /**
         * @var array
         */
        private $roles;
    
        /**
         * GroupType constructor.
         * @param $roles
         */
        public function __construct($roles)
        {
            $this->roles = $roles;
        }
    
        /**
         * @var EntityType
         */
        private $entityType;
    
        /**
         * @param EntityType $entityType
         * @internal param $imagepath
         */
        public function setEntityType(
            $entityType
        )
        {
            $this->entityType = $entityType;
        }
    
        /**
         * Mainly for testing
         * @return bool
         */
        public function hasEntityType(){
            return $this->entityType instanceof EntityType;
        }
    
        /**
         * @param FormBuilderInterface $builder
         * @param array $options
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
    
            $builder
                ->add('enabled',
                    CheckboxType::class,
                    [
                        'label' => 'smartadmin.user.active',
                        'required' => false
                    ]
                )->add('groups',
                    $this->entityType,
                    [
                        'class' => Group::class,
                        'property' => 'name',
                        'multiple' => true,
                        'expanded' => true,
                        'required' => false,
                        'label' => 'smartadmin.user.groups'
                    ]
                )->add('username',
                    TextType::class,
                    [
                        'label' => 'form.username',
                        'translation_domain' => 'FOSUserBundle'
                    ]
                )->add('email',
                    EmailType::class,
                    [
                        'label' => 'form.email',
                        'translation_domain' => 'FOSUserBundle',
                        'constraints' => [new Email()]
                    ]
                )->add('firstname',
                    TextType::class,
                    [
                        'label' => 'smartadmin.user.firstname'
                    ]
                )->add('lastname',
                    TextType::class,
                    [
                        'label' => 'smartadmin.user.lastname'
                    ]
                )->add('gender',
                    ChoiceType::class,
                    [
                        'label' => 'smartadmin.user.gender',
                        'choices' => [1 => 'smartadmin.user.gender_male', 2 => 'smartadmin.user.gender_female']
                    ]
    
                )->add('plainPassword', RepeatedType::class,
                    [
                        'type' => PasswordType::class,
                        'options' => array('translation_domain' => 'FOSUserBundle'),
                        'first_options' => array('label' => 'form.new_password'),
                        'second_options' => array('label' => 'form.new_password_confirmation'),
                        'invalid_message' => 'fos_user.password.mismatch',
                        'required' => false
                    ]
                );
        }
    
        /**
         * @param OptionsResolver $resolver
         */
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => 'UserBundle\Entity\User',
                'csrf_token_id' => 'profile'
            ));
        }
    }
    

    在我的控制器中:

    $oUser = $this->userRepository->find($userId);
    
    $userType = new UserType($this->roleService->getAvailableRoles());
    $userType->setEntityType(new EntityType($this->doctrine));
    $oForm = $this->formFactory->createBuilder($userType)
        ->setData($oUser)
        ->getForm();
    

    为了测试这个表单,我创建了一个替换实体类型,它只提供了像 entityType 一样工作所需的方法:

    namespace UserBundle\Tests\Form\Type;
    
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\Form\FormView;
    use Symfony\Component\Form\Util\StringUtil;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    
    /**
     * Class EntityTypeSimulator
     * @package UserBundle\Tests\Form\Type
     */
    class EntityTypeSimulator extends AbstractType
    {
    
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
        }
    
        /**
         * {@inheritdoc}
         */
        public function buildView(FormView $view, FormInterface $form, array $options)
        {
        }
    
        /**
         * {@inheritdoc}
         */
        public function finishView(FormView $view, FormInterface $form, array $options)
        {
        }
    
        /**
         * {@inheritdoc}
         */
        public function setDefaultOptions(OptionsResolverInterface $resolver)
        {
            if (!$resolver instanceof OptionsResolver) {
                throw new \InvalidArgumentException(sprintf('Custom resolver "%s" must extend "Symfony\Component\OptionsResolver\OptionsResolver".', get_class($resolver)));
            }
    
            $this->configureOptions($resolver);
        }
    
        /**
         * Configures the options for this type.
         *
         * @param OptionsResolver $resolver The resolver for the options.
         */
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'multiple' => false,
                'expanded' => false,
                'class' => null,
                'property' => true,
                'invalid_message' => null
            ));
        }
    
        /**
         * {@inheritdoc}
         */
        public function getName()
        {
            // As of Symfony 2.8, the name defaults to the fully-qualified class name
            return get_class($this);
        }
    
        /**
         * Returns the prefix of the template block name for this type.
         *
         * The block prefixes default to the underscored short class name with
         * the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
         *
         * @return string The prefix of the template block name
         */
        public function getBlockPrefix()
        {
            $fqcn = get_class($this);
            $name = $this->getName();
    
            // For BC: Use the name as block prefix if one is set
            return $name !== $fqcn ? $name : StringUtil::fqcnToBlockPrefix($fqcn);
        }
    
        /**
         * {@inheritdoc}
         */
        public function getParent()
        {
            return 'Symfony\Component\Form\Extension\Core\Type\FormType';
        }
    }
    

    现在是我的测试用例:

    namespace UserBundle\Tests\Form\Type;
    
    
    use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
    use Symfony\Component\Form\Test\TypeTestCase;
    use Symfony\Component\Validator\Validation;
    use UserBundle\Entity\User;
    use UserBundle\Form\Type\UserType;
    
    /**
     * Class UserTypeTest
     * @package UserBundle\Tests\Form\Type
     */
    class UserTypeTest extends TypeTestCase
    {
    
        /**
         * Load the ValidatorExtension so RepeatedType can resolve 'invalid_message'
         * @return array
         */
        protected function getExtensions()
        {
            return array(new ValidatorExtension(Validation::createValidator()));
        }
    
       public function testGroupType(){
            $formData['user'] = [
                'enabled' => 1,
                'groups' => [1,2,3],
                'username' => 'Test username',
                'email' => 'test@email.de',
                'firstname' => 'Test Firstname',
                'lastname' => 'Test Lastname',
                'gender' => 1,
                'plainPassword' => ['first' => 'Test Password', 'second' => 'Test Password']
            ];
    
            $roles = ['ROLE_USER'];
            $entity = new User('Test name');
    
            $type = new UserType($roles);
            $type->setEntityType(new EntityTypeSimulator());
            $oFormBuilder = $this->factory->createBuilder();
    
            $oFormBuilder->add('user', $type);
            $oFormBuilder->setData(['user' => $entity]);
    
            $oForm = $oFormBuilder->getForm();
            $oForm->submit($formData);
            $oForm->handleRequest();
    
            $this->assertTrue($oForm->isSynchronized());
    
            $view = $oForm->createView();
            $children = $view->children['user']->children;
    
            foreach (array_keys($formData['user']) as $key) {
                $this->assertArrayHasKey($key, $children);
            }
            $this->assertEquals($formData['user']['username'], $entity->getUsername());
            $this->assertEquals($formData['user']['email'], $entity->getEmail());
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2017-08-16
      • 1970-01-01
      • 2012-10-19
      • 1970-01-01
      • 2018-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多