首页 > 解决方案 > Symfony 4 中 FormErrorSerializer 的单元测试 - 始终有效的形式

问题描述

我正在尝试为 FormErrorSerializer 编写一个单元测试,它将 Symfony $form->getErrors() 转换为可读数组。

我目前的方法是创建表单、为其提供数据并查找验证错误,但表单始终有效。无论我提供什么数据,我都不会收到任何错误。

在正常的 REST 请求/响应中,它运行良好,我收到了适当的错误消息。我需要帮助以获取单元测试中的错误消息。

namespace App\Tests\Unit;

use App\Form\UserType;
use App\Serializer\FormErrorSerializer;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Translation\Translator;


class FormErrorSerializerTest extends TypeTestCase
{
    /**
     * ValidatorExtensionTrait needed for invalid_options
     * https://github.com/symfony/symfony/issues/22593
     */
    use ValidatorExtensionTrait;

    public function testConvertFormToArray(){
        $form_data = [
            'email' => 'test',
            'plainPassword' => [
                'pass' => '1',
                'pass2' => '2'
            ]
        ];

        $translator = new Translator('de');

        $form = $this->factory->create(UserType::class);

        $form->submit($form_data);

        if( $form->isValid() ) {
            echo  "Form is valid"; exit;
        }

        $formErrorSerializer = new FormErrorSerializer($translator);

        $errors = $formErrorSerializer->convertFormToArray($form);

        print_r($errors); exit;
    }
}

在序列化器下方找到:

namespace App\Serializer;

use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * Serializes invalid Form instances.
 */
class FormErrorSerializer
{
    private $translator;

    public function __construct(TranslatorInterface $translator)
    {
        $this->translator = $translator;
    }

    public function convertFormToArray(FormInterface $data)
    {
        $form = $errors = [];

        foreach ($data->getErrors() as $error) {
            $errors[] = $this->getErrorMessage($error);
        }

        if ($errors) {
            $form['errors'] = $errors;
        }

        $children = [];
        foreach ($data->all() as $child) {
            if ($child instanceof FormInterface) {
                $children[$child->getName()] = $this->convertFormToArray($child);
            }
        }

        if ($children) {
            $form['children'] = $children;
        }

        return $form;
    }

    private function getErrorMessage(FormError $error)
    {
        if (null !== $error->getMessagePluralization()) {
            return $this->translator->transChoice(
                $error->getMessageTemplate(),
                $error->getMessagePluralization(),
                $error->getMessageParameters(),
                'validators'
            );
        }

        return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
    }
}

标签: formsunit-testingsymfonyphpunitsymfony4

解决方案


好的,我能够以两种不同的方式做到这一点。

第一个解决方案是在 getExtensions 方法中加载验证器。TypeTestCase 中的工厂不带验证器。因此,您不仅必须加载验证器,还必须明确指定验证。您可以使用 symfony 提供的方法指定验证,或者如果您正在使用验证器,则可以直接将验证器指向 YAML 或 xml 文件。

public function getExtensions()
{
    $validator = (new ValidatorBuilder())
        ->addYamlMapping("path_to_validations.yaml")
        ->setConstraintValidatorFactory(new ConstraintValidatorFactory())
        ->getValidator();

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

    return $extensions;
}

但是,我没有使用上述方法。我选择了更好的解决方案。由于我的测试用例非常复杂(因为它需要多个服务),我使用了 Symfony 的 KernelTestCase 提供的特殊容器。它在测试中提供私有服务,并且它提供的工厂带有验证器和验证,就像您在控制器中编写代码一样。您不需要显式加载验证器。在下面找到我扩展 KernelTestCase 的最终测试。

namespace App\Tests\Unit\Serializer;

use App\Entity\User;
use App\Form\UserType;
use App\Serializer\FormErrorSerializer;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;

class FormErrorSerializerTest extends KernelTestCase
{
    /**
     * {@inheritDoc}
     */
    protected function setUp()
    {
        $kernel = self::bootKernel();
    }

    public function testConvertFormToArray_invalidData(){
        $form_data = [
            'email' => 'test',
            'plainPassword' => [
                'pass' => '1111',
                'pass2' => ''
            ]
        ];

        $user = new User();
        $user->setEmail($form_data['email']);
        $user->setPlainPassword($form_data['plainPassword']['pass']);

        $factory = self::$container->get(FormFactoryInterface::class);
        /**
         * @var FormInterface $form
         */
        $form = $factory->create(UserType::class, $user);

        $form->submit($form_data);

        $this->assertTrue($form->isSubmitted());
        $this->assertFalse($form->isValid());

        $translator = self::$container->get(TranslatorInterface::class);
        $formErrorSerializer = new FormErrorSerializer($translator);
        $errors = $formErrorSerializer->convertFormToArray($form);

        $this->assertArrayHasKey('errors', $errors['children']['email']);
        $this->assertArrayHasKey('errors', $errors['children']['plainPassword']['children']['pass']);
    }

    public function testConvertFormToArray_validData(){
        $form_data = [
            'email' => 'test@example.com',
            'plainPassword' => [
                'pass' => 'somepassword@slkd12',
                'pass2' => 'somepassword@slkd12'
            ]
        ];

        $user = new User();
        $user->setEmail($form_data['email']);
        $user->setPlainPassword($form_data['plainPassword']['pass']);

        $factory = self::$container->get(FormFactoryInterface::class);
        /**
         * @var FormInterface $form
         */
        $form = $factory->create(UserType::class, $user);

        $form->submit($form_data);

        $this->assertTrue($form->isSubmitted());
        $this->assertTrue($form->isValid());

        $translator = self::$container->get(TranslatorInterface::class);
        $formErrorSerializer = new FormErrorSerializer($translator);
        $errors = $formErrorSerializer->convertFormToArray($form);

        $this->assertArrayNotHasKey('errors', $errors['children']['email']);
        $this->assertArrayNotHasKey('errors', $errors['children']['plainPassword']['children']['pass']);
    }
}

请注意,Symfony 4.1 有一个特殊的容器,允许获取私有服务。

self::$kernel->getContainer();不是特殊容器。它不会获取私人服务。

但是,self::$container;是在测试中提供私有服务的特殊容器。

更多关于这里的信息。


推荐阅读