2

(Using framework Symfony 4.4)

I try to learn about how to test a Form having a field being an EntityType form field.

See exemple bellow :

class VaccinationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('vaccine_brand_uid', EntityType::class, ['class' => ViewVaccineBrand::class])
                ->add('administration_date', DateTimeType::class, [
                      'widget' => 'single_text',
                      'model_timezone' => 'UTC',
                      'view_timezone' => 'UTC',
                  ]);
    }



    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        $resolver->setDefaults([
            'data_class' => VaccinationInput::class,
            'method' => 'POST',
            'csrf_protection' => false
        ]);
    }
}

As you can see the field vaccine_brand_uid is an EntityType field so it ensure the given value when submitting the form is part of the ViewVaccineBrand table.

Here's bellow the related VaccinationInput object :

namespace App\Services\Domain\Vaccination\DTO;

use App\Entity\ViewVaccineBrand;
...

class VaccinationInput
{

    /**
     * @Assert\Type(type=ViewVaccineBrand::class, message="api_missingBrand")
     * @Assert\NotBlank(message="api_missingBrand")
     * @var ViewVaccineBrand|int
     */
    public $vaccine_brand_uid;
   
    /**
     * @Assert\DateTime(message="api_missingAdministrationDate")
     */
    public $administration_date;
}

Create a base class for testing form with EntityType fields

So while trying to create a test class for this form, I found this exemple in the Symfony repository : https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php

So I copy/paste this class to adapt it for my own tests !

And I adapt it a little so it is more reusable (adding the getTypes() function) :

/**
 * Inspired by https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
 */
abstract class EntityTypeTestCase extends TypeTestCase
{
    /**
     * @var EntityManager
     */
    protected $em;

    /**
     * @var MockObject&ManagerRegistry
     */
    protected $emRegistry;

    protected function setUp(): void
    {
        $this->em = DoctrineTestHelper::createTestEntityManager();
        $this->emRegistry = $this->createRegistryMock('default', $this->em);

        parent::setUp();

        $schemaTool = new SchemaTool($this->em);

        $classes = array_map(fn($c) => $this->em->getClassMetadata($c), $this->registeredTypes());

        try {
            $schemaTool->dropSchema($classes);
        } catch (\Exception $e) {
        }

        try {
            $schemaTool->createSchema($classes);
        } catch (\Exception $e) {
        }
    }

    protected function tearDown(): void
    {
        parent::tearDown();

        $this->em = null;
        $this->emRegistry = null;
    }

    protected function getExtensions(): array
    {
        return array_merge(parent::getExtensions(), [
            new DoctrineOrmExtension($this->emRegistry),
        ]);
    }

    protected function createRegistryMock($name, $em): ManagerRegistry
    {
        $registry = $this->createMock(ManagerRegistry::class);
        $registry->expects($this->any())
            ->method('getManager')
            ->with($this->equalTo($name))
            ->willReturn($em);

        return $registry;
    }

    protected function persist(array $entities)
    {
        foreach ($entities as $entity) {
            $this->em->persist($entity);
        }

        $this->em->flush();
    }

    protected function getTypes()
    {
        return array_merge(parent::getTypes(), []);
    }

    /**
     * @return array An array of current registered entity type classes.
     */
    abstract protected function registeredTypes(): array;
}

Create a specific test class for my Vaccination form

So I want to test my VaccinationType form, here's what I did bellow.

~/symfony/tests/Service/Domain/Vaccination/Type/VaccinationTypeTest

<?php

namespace App\Tests\Service\Domain\Vaccination\Type;
...
class VaccinationTypeTest extends EntityTypeTestCase
{
    public function testWhenMissingBrandThenViolation()
    {
        $model = new VaccinationInput();
        $entity1 = (new ViewVaccineBrand())->setVaccineBrandName('test')->setIsActive(true)->setVaccineCode('code');
        $this->persist([$entity1]);

        // $model will retrieve data from the form submission; pass it as the second argument
        $form = $this->factory->create(VaccinationType::class, $model);

        $form->submit(['vaccine_brand_uid' => 2, 'administration_date' => DateTime::formatNow()]);

        $violations = $form->getErrors(true);
        $this->assertCount(1, $violations); // There is no vaccine brand for uid 2
    }

    protected function getTypes()
    {
        return array_merge(parent::getTypes(), [new EntityType($this->emRegistry)]);
    }

    protected function registeredTypes(): array
    {
        return [
            ViewVaccineBrand::class,
            ViewVaccineCourse::class,
        ];
    }

    protected function getExtensions(): array
    {
        $validator = Validation::createValidator();

        return array_merge(parent::getExtensions(), [
            new ValidatorExtension($validator),
        ]);
    }
}

Actual result

The actual result of the php bin/phpunit execution is as follow :

There was 1 error:

  1. App\Tests\Service\Domain\Vaccination\Type\VaccinationTypeTest::testWhenMissingBrandThenViolation Symfony\Component\Form\Exception\RuntimeException: Class "App\Entity\ViewVaccineBrand" seems not to be a managed Doctrine entity. Did you forget to map it?

/app/xxxxapi/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:203 /app/xxxxapi/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:203 /app/xxxxapi/vendor/symfony/options-resolver/OptionsResolver.php:1035 /app/xxxxapi/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:130 /app/xxxxapi/vendor/symfony/options-resolver/OptionsResolver.php:915 /app/xxxxapi/vendor/symfony/options-resolver/OptionsResolver.php:824 /app/xxxxapi/vendor/symfony/form/ResolvedFormType.php:97 /app/xxxxapi/vendor/symfony/form/FormFactory.php:76 /app/xxxxapi/vendor/symfony/form/FormBuilder.php:94 /app/xxxxapi/vendor/symfony/form/FormBuilder.php:244 /app/xxxxapi/vendor/symfony/form/FormBuilder.php:195 /app/xxxxapi/vendor/symfony/form/FormFactory.php:30 /app/xxxxapi/tests/Service/Domain/Vaccination/Type/VaccinationTypeTest.php:23

I think that's because for some reason, the entitymanager created in the EntityTypeTestCase is not the same as the one used at /app/xxxxx/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:203 ...

But I don't know how to specify that, in the case of this test, I want that DoctrineType (which is parent of EntityType) use this special test entityManager instead of the default one.

Expected result

Make the test work an the assertion should be successfull.

Edit

I add the Entity for extra informations


namespace App\Entity;

/**
 * VaccineBrand
 *
 * @ORM\Table(name="dbo.V_HAP_VACCINE_BRAND")
 * @ORM\Entity(repositoryClass="App\Repository\ViewVaccineBrandRepository")
 */
class ViewVaccineBrand
{
    /**
     * @var int
     *
     * @ORM\Column(name="VACCINE_BRAND_UID", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $vaccineBrandUid;

    /**
     * @var string
     * @ORM\Column(name="VACCINE_BRAND_NAME", type="string", nullable=false)
     */
    protected $vaccineBrandName;

    /**
     * @var string
     * @ORM\Column(name="VACCINE_BRAND_CODE", type="string", nullable=true)
     */
    protected $vaccineBrandCode;
// ... Other fields are not relevant
}

PS

I already read those one with no luck :

vincent PHILIPPE
  • 975
  • 11
  • 26
  • Does the form work outside of a testcase, in your application? A possible cause for this error could also be an invalid entity configuration, like missing/incorrect namespace, missing attributes, etc. Could you verify that the entity is correct or post the entity source? – Brent Dec 30 '22 at 11:01
  • Why are you persisting data in your unit tests? `$this->persist([$entity1]);` should be removed or done properly. – domagoj Jan 02 '23 at 08:15
  • Hello Brent, the form work outside of the tests case. – vincent PHILIPPE Jan 02 '23 at 21:57
  • Hi domagoj, This is done through the mocked EntityManager. By the way, this is the proper way to do it see https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php#L143 – vincent PHILIPPE Jan 02 '23 at 22:04
  • can you post your entity? – dev_mustafa Jan 11 '23 at 08:37
  • @dev_mustafa I've added the ViewVaccineBrand entity for extra information – vincent PHILIPPE Jan 11 '23 at 09:59
  • What about `VaccinationInput`? Is that a related entity with a *mapped* relation to `ViewVaccineBrand`? – Arleigh Hix Jan 11 '23 at 17:42
  • To which entity is `VaccinationType` bound? That entity's source code should be added to your question. – Arleigh Hix Jan 11 '23 at 17:45
  • Do you maybe have a simple typo somewhere in te filename or namespace etc... The same error message here was solved just because a misspelling: https://stackoverflow.com/questions/14924849/class-seems-not-to-be-a-managed-doctrine-entity-did-you-forget-to-map-it. I can not see it in your examples because you excluded those lines I guess – Julesezaar Jan 12 '23 at 12:01
  • @ArleighHix thank you for your comment, I 've added `VaccinationInput` for extra information. As you can see this is a simple DTO. Also, `VaccinationType` is bound to `VaccinationInput`. – vincent PHILIPPE Jan 18 '23 at 07:49
  • You have an `EntityType` for an integer column that is not mapped to an Entity. Did you forget to add a `OneToOne` or `OneToMany` relation? – Arleigh Hix Jan 18 '23 at 16:29
  • Hello @ArleighHix, the DTO is not an entity. It doesn't have to be mapped by any relation ! EntityType is used to tell that this field should be a valid Entity (A valid ViewVaccineBrand object). Anyway, a DTO is a valid practice, transfering data after having validate it is pretty common. – vincent PHILIPPE Jan 19 '23 at 13:58
  • `EntityType` is for related entities, that's why you get the "...seems not to be a managed Doctrine entity" message – Arleigh Hix Jan 19 '23 at 15:12

1 Answers1

0

This is a complicated situation, so I will provide an abbreviated outline of what I believe you need to do.

  • Change EntityType (for related entity) to ChoiceType
    • Use a CallbackChoiceLoader that queries the DB for all the uid fields indexed by name (or whatever you want to be the label), i.e.:['BigPharm'=>123, 'Other Brand'=>564, ]
  • Change @Assert\Type to @Assert\Choice
    • Use the callback option with a method that will call array_values on the result of the same as the CallbackChoiceLoader above, i.e.:[123, 564, ]
Arleigh Hix
  • 9,990
  • 1
  • 14
  • 31