Home > Software design >  Symfony TypeTestCase, Error: Class "doctrine.orm.validator.unique" not found
Symfony TypeTestCase, Error: Class "doctrine.orm.validator.unique" not found

Time:08-29

Intention: I want to test if the validations I want are in place on the School entity, for which I want to write a test class extending TypeTestCase

Questions/problems:

  1. I want to clear the error Error: Class "doctrine.orm.validator.unique" not found
  2. I want to assert the error messages for each constraints of my elements. When I remove #[UniqueEntity('name')] from the model, then problem one vanishes but still the assertion self::assertCount(1, $form->getErrors()); fails. Which means $form->getErrors() does not have the validation error for the name being blank.

I am trying to write a symfony test a symfony Form type with a DB entity, with the following (stripped) definitions:

namespace App\Entity;

use App\Repository\SchoolRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: SchoolRepository::class)]
// >>>>>>> If I remove it problem 1 will be solved 
#[UniqueEntity('name')]
class School implements TenantAwareInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[Assert\NotBlank]
    #[ORM\Column(type: 'string', length: 255, unique: true)]
    private $name;
    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }
}

And form being:

namespace App\Form;

use App\Entity\School;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

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

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => School::class,
            'required' => false
        ]);
    }
}

The test:

namespace App\Tests\Integration\Form;

use App\Entity\School;
use App\Form\SchoolType;
use Doctrine\Persistence\ManagerRegistry;
use Mockery as m;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
use Symfony\Contracts\Translation\TranslatorInterface;

class SchoolTypeTest extends TypeTestCase
{
    use ValidatorExtensionTrait;

    protected function getExtensions(): array
    {
        $validator = Validation::createValidatorBuilder()
            ->enableAnnotationMapping()
            ->addDefaultDoctrineAnnotationReader()
            ->getValidator();

        $mockedManagerRegistry = m::mock(ManagerRegistry::class, ['getManagers' => []]);

        return [
            new ValidatorExtension($validator),
            new DoctrineOrmExtension($mockedManagerRegistry),
        ];
    }

    public function testValidationReturnsError()
    {
        $school = new School();
        $form = $this->factory->create(SchoolType::class, $school);

        $form->submit([]);

        self::assertTrue($form->isSynchronized());
        self::assertFalse($form->isValid());

        // >>>>>>> I want this to assert, problem 2
        self::assertCount(1, $form->getErrors());
    }
}

CodePudding user response:

In short, I ended up writing adding a mocked UniqueEntity validator. I added some generic codes to ease testing other form types, which are as following:

A base for tests:

namespace App\Tests\Service;

use Doctrine\Persistence\ManagerRegistry;
use Mockery as m;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;

class AppTypeWithValidationTestCase extends TypeTestCase
{
    use ValidatorExtensionTrait;

    protected function getExtensions(): array
    {
        $mockedManagerRegistry = m::mock(
            ManagerRegistry::class,
            [
                'getManagers' => []
            ]
        );

        $factory = new AppConstraintValidatorFactory();
        $factory->addValidator(
            'doctrine.orm.validator.unique',
            m::mock(UniqueEntityValidator::class, [
                'initialize' => null,
                'validate' => true,
            ])
        );

        $validator = Validation::createValidatorBuilder()
            ->setConstraintValidatorFactory($factory)
            ->enableAnnotationMapping()
            ->addDefaultDoctrineAnnotationReader()
            ->getValidator();

        return [
            new ValidatorExtension($validator),
            new DoctrineOrmExtension($mockedManagerRegistry),
        ];
    }

    // *** Following is a helper function which ease the way to 
    // *** assert validation error messages
    public static function assertFormViewHasError(FormView $formElement, string $message): void
    {
        foreach ($formElement->vars['errors'] as $error) {
            self::assertSame($message, $error->getMessage());
        }
    }
}

A constraint validator which accepts a validator, it is needed so we can add the (mocked) definition of UniqeEntity:

namespace App\Tests\Service;

use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\ConstraintValidatorInterface;

class AppConstraintValidatorFactory extends ConstraintValidatorFactory
{
    public function addValidator(string $className, ConstraintValidatorInterface $validator): void
    {
        $this->validators[$className] = $validator;
    }
}

And the final unit test class:

<?php

declare(strict_types=1);

namespace App\Tests\Unit\Form;

use App\Entity\School;
use App\Form\SchoolType;
use App\Tests\Service\AppTypeWithValidationTestCase;

class SchoolTypeTest extends AppTypeWithValidationTestCase
{
    public function testValidationReturnsError() {
        $input = [
            // *** Note that 'name' is missing here
            'is_enabled' => true,
        ];
        
        $school = new School();
        $form = $this->factory->create(SchoolType::class, $school);

        $form->submit($input);

        self::assertTrue($form->isSynchronized());
        self::assertFalse($form->isValid());

        $view = $form->createView();

        self::assertFormViewHasError($view->children['name'], 'This value should not be blank.');
    }
}
  • Related