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:
- I want to clear the error
Error: Class "doctrine.orm.validator.unique" not found
- 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 assertionself::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.');
}
}