Symfony2: Testing entity validation constraints

Does anyone have a good way to unit test an entity's validation constraints in Symfony2?

Ideally I want to have access to the Dependency Injection Container within the unit test which would then give me access to the validator service. Once I have the validator service I can run it manually:

$errors = $validator->validate($entity);

I could extend WebTestCase and then create a client to get to the container as per the docs however it doesn't feel right. The WebTestCase and client read in the docs as more of a facility to test actions as a whole and therefore it feels broken to use it to unit test an entity.

So, does anyone know how to either a) get the container or b) create the validator inside a unit test?


Solution 1:

Ok since this got two votes I guess other people are interested.

I decided to get my shovel out and was pleasantly surprised (so far anyway) that this wasn't at all difficult to pull off.

I remembered that each Symfony2 component can be used in a stand alone mode and therefore that I could create the validator myself.

Looking at the docs at: https://github.com/symfony/Validator/blob/master/ValidatorFactory.php

I realised that since there was a ValidatorFactory it was trivial to create a validator (especially for validation done by annotations which I am, although if you look at the docblock on the page I linked above you'll also find ways to validate xml and yml).

First:

# Symfony >=2.1
use Symfony\Component\Validator\Validation;
# Symfony <2.1
use Symfony\Component\Validator\ValidatorFactory;

and then:

# Symfony >=2.1
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
# Symfony <2.1
$validator = ValidatorFactory::buildDefault()->getValidator();

$errors = $validator->validate($entity);

$this->assertEquals(0, count($errors));

I hope this helps anyone else whose conscience wouldn't allow them to just use WebTestCase ;).

Solution 2:

We end up rolling your own base test case to access the dependency container from within a test case. Here the class in question:

<?php

namespace Application\AcmeBundle\Tests;

// This assumes that this class file is located at:
// src/Application/AcmeBundle/Tests/ContainerAwareUnitTestCase.php
// with Symfony 2.0 Standard Edition layout. You may need to change it
// to fit your own file system mapping.
require_once __DIR__.'/../../../../app/AppKernel.php';

class ContainerAwareUnitTestCase extends \PHPUnit_Framework_TestCase
{
    protected static $kernel;
    protected static $container;

    public static function setUpBeforeClass()
    {
        self::$kernel = new \AppKernel('dev', true);
        self::$kernel->boot();

        self::$container = self::$kernel->getContainer();
    }

    public function get($serviceId)
    {
        return self::$kernel->getContainer()->get($serviceId);
    }
}

With this base class, you can now do this in your test methods to access the validator service:

$validator = $this->get('validator');

We decided to go with a static function instead of the class constructor but you could easily change the behavior to instantiate the kernel into the constructor directly instead of relying on the static method setUpBeforeClass provided by PHPUnit.

Also, keep in mind that each single test method in you test case won't be isolated fro, each others because the container is shared for the whole test case. Making modification to the container may have impact on you other test method but this should not be the case if you access only the validator service. However, this way, the test cases will run faster because you will not need to instantiate and boot a new kernel for each test methods.

For the sake of reference, we find inspiration for this class from this blog post. It is written in French but I prefer to give credit to whom it belongs :)

Regards,
Matt

Solution 3:

I liked Kasheens answer, but it doesn't work for Symfony 2.3 anymore. There are little changes:

use Symfony\Component\Validator\Validation;

and

$validator = Validation::createValidatorBuilder()->getValidator();

If you want to validate Annotations for instance, use enableAnnotationMapping() like below:

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

the rest stays the same:

$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));

Solution 4:

With Symfony 2.8, it seems that you can now use the AbstractConstraintValidatorTest class this way :

<?php
namespace AppBundle\Tests\Constraints;

use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use AppBundle\Constraints\MyConstraint;
use AppBundle\Constraints\MyConstraintValidator;
use AppBundle\Entity\MyEntity;
use Symfony\Component\Validator\Validation;

class MyConstraintValidatorTest extends AbstractConstraintValidatorTest
{
    protected function getApiVersion()
    {
        return Validation::API_VERSION_2_5;
    }

    protected function createValidator()
    {
        return new MyConstraintValidator();
    }

    public function testIsValid()
    {
        $this->validator->validate(null, new MyEntity());

        $this->assertNoViolation();
    }

    public function testNotValid()
    {
        $this->assertViolationRaised(new MyEntity(), MyConstraint::SOME_ERROR_NAME);
    }
}

You have got a good sample with the IpValidatorTest class