Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony FormType testing deal with EntityType

I've a question to Symfony FormType testing. http://symfony.com/doc/current/cookbook/form/unit_testing.html

In my form types the entity type is common. Testing the form types with a doctrine entity form type is horrible.

This is my form field.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('products', 'entity', array(
        'class'     => 'AcmeDemoBundle:Product',
        'label'     => 'Product',
        'property'  => 'name',
        'required'  => false,
        'mapped'    => true,
        'multiple'  => true,
        'expanded'  => true
    ));
}

And here is the mock for the field.

private function getEntityTypeMock()
{
    $entityRepositoryMock = $this->getMockBuilder('Doctrine\ORM\EntityRepository')
        ->disableOriginalConstructor()
        ->getMock()
    ;

    $entityRepositoryMock->expects($this->once())
        ->method('findAll')
        ->will($this->returnValue(array()));

    $classMetaDataMock = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')
        ->disableOriginalConstructor()
        ->getMock();

    $mockEntityManager = $this->getMockBuilder('Doctrine\ORM\EntityManager')
        ->disableOriginalConstructor()
        ->getMock();

    $mockEntityManager->expects($this->any())
        ->method('getClassMetadata')
        ->will($this->returnValue($classMetaDataMock));

    $mockEntityManager->expects($this->once())
        ->method('getRepository')
        ->will($this->returnValue($entityRepositoryMock));

    $mockRegistry = $this->getMockBuilder('Doctrine\Bundle\DoctrineBundle\Registry')
        ->disableOriginalConstructor()
        ->getMock();

    $mockRegistry->expects($this->any())
        ->method('getManagerForClass')
        ->will($this->returnValue($mockEntityManager));

    $mockEntityType = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\Type\EntityType')
        ->setMethods(array('getName'))
        ->setConstructorArgs(array($mockRegistry))
        ->getMock();

    $mockEntityType->expects($this->any())->method('getName')
        ->will($this->returnValue('entity'));

    return $mockEntityType;
}

Is this really the correct way? Inside the TypeTestCase I don't have access to anything, no container no kernel, nothing. This makes testing a form type pretty hard and frustating.

Is there a better way to test the form types? Or a easyier way to deal with types that have an ORM dependency?

Cheers.

like image 997
createproblem Avatar asked Jun 06 '14 10:06

createproblem


1 Answers

I struggled with unit testing something that relies on a service container a great deal. At first I tried mocking everything, like you did. This can make a unit test pass with great effort (services have a tendency to rely on still other services in Symfony, which will also have to be mocked), but it takes even more effort to make sure that passing the test means that it would work with the data you want it to work with.

Additionally, unit testing a database is notoriously difficult and rarely discussed. I am not sure if what I'm sharing is the "best" answer, but it is an answer which worked for me and it helps to unit test the real services. As such I have found it to be a more effective method to testing than mocking the services.

This is based on a great article, which, of course, I can't locate now (I will update this if I find it to credit them).

Basically, you can set up your bundles to have containers in testing.

composer.json:

"require-dev": {
    "sensio/framework-extra-bundle": ">=3.0",
    "symfony/asset": ">=3.2"
}

Then create a config.yml with any services you might need and the bare minimum for Symfony forms:

framework:
    secret: 'shh'
    form: ~
    validation: { enable_annotations: true }
    session:
        storage_id: session.storage.mock_file

doctrine:
    # your doctrine settings if you want to test against the DB

Create an AppKernel class.

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        return array(
            new FrameworkBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            // any other bundles you need
        );
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load(__DIR__.'/config.yml');

    }

    public function getLogDir()
    {
        return '/tmp/log/' . $this->environment;
    }
}

Finally, I create a helper class in my base TestCase:

protected function getContainer()
{
    if (!isset($this->kernel)) {
        $this->kernel = new AppKernel('test', true);
        $this->kernel->boot();
    }
    if (!isset($this->container)) {
        $this->container = $this->kernel->getContainer();
    }
    return $this->container;
}

Now you can access any services that you have registered like this:

public function testContainerAccess()
{
    $this->assertTrue(is_object($this->getContainer());
    $this->assertTrue($this->getContainer()->get('doctrine.orm.entity_manager') instanceof \Doctrine\ORM\EntityManagerInterface);
}

Testing against a database is always tricky and is a separate can of worms. In this case the easiest thing to do would probably be to create a separate testing schema and run your queries against that.

Hope this helps.

like image 79
smcjones Avatar answered Sep 20 '22 17:09

smcjones