使用实体对Symfony表单进行单元测试


Unit testing Symfony forms with entities

我很难对内部有"entity"字段的Symfony表单进行单元测试。

我在这里和这里找到了潜在的解决方案,但我无法使它们发挥作用。

这是我的代码:

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::testSubmitValidDataSymfony''Component''选项解算器''异常''未定义选项异常:选项"attr"、"class"、"empty_value"、,"query_builder"、"required"不存在。已知的选项有:"。

我需要用测试来覆盖这些表格,我完全没有想法了。有什么帮助吗?

之所以发生这种情况,是因为OptionsResolver不知道这些选项。EntityType mock应该声明它们:

对于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,
                )
            );
        }
    )
);

从symfony 2.7起,'property'选项已被'choice_label'取代。

文档

我想介绍一下我的解决方案。首先,我将实体类型注入到我的表单中。以下是我在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();

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

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());
    }
}