web-dev-qa-db-fra.com

Symfony2: test des contraintes de validation d'entité

Quelqu'un a-t-il un bon moyen de tester les contraintes de validation d'une entité dans Symfony2?

Idéalement, je souhaite avoir accès au conteneur d'injection de dépendance dans le test unitaire, ce qui me permettrait ensuite d'accéder au service de validation. Une fois que j'ai le service de validation, je peux l'exécuter manuellement:

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

Je pourrais prolonger WebTestCase puis créer une client pour accéder au conteneur conformément à la documentation, mais cela ne semble pas correct. Les WebTestCase et client sont lues dans la documentation comme davantage une installation pour tester les actions dans son ensemble et, par conséquent, il semble inutilisable de l'utiliser pour tester une entité.

Alors, est-ce que quelqu'un sait comment soit a) obtenir le conteneur ou b) créer le validateur dans un test unitaire?

47
Kasheen

Ok, puisque cela a eu deux votes, je suppose que d’autres personnes sont intéressées.

J'ai décidé de sortir ma pelle et j'ai été agréablement surpris (jusqu'à présent, de toute façon) que ce ne soit pas du tout difficile à retirer.

Je me suis rappelé que chaque composant Symfony2 peut être utilisé en mode autonome et que, par conséquent, je pouvais créer le validateur moi-même.

En regardant les documents sur: https://github.com/symfony/Validator/blob/master/ValidatorFactory.php

Je me suis rendu compte que puisqu'il y avait une ValidatorFactory, il était facile de créer un validateur (en particulier pour la validation faite par les annotations que je suis, bien que si vous regardez le docblock sur la page que j'ai liée ci-dessus, vous trouverez également des moyens de valider xml et yml ).

Premier:

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

et alors:

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

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

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

J'espère que cela aidera tous les autres dont la conscience ne leur permettrait pas d'utiliser simplement WebTestCase;).

58
Kasheen

Nous finissons par lancer votre propre scénario de test de base pour accéder au conteneur de dépendance à partir d'un scénario de test. Voici la classe en 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);
    }
}

Avec cette classe de base, vous pouvez maintenant le faire dans vos méthodes de test pour accéder au service de validation:

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

Nous avons décidé d'utiliser une fonction statique au lieu du constructeur de classe, mais vous pouvez facilement changer le comportement pour instancier directement le noyau dans le constructeur au lieu de vous fier à la méthode statique setUpBeforeClass fournie par PHPUnit.

De plus, gardez à l'esprit que chaque méthode de test de votre scénario de test ne sera pas isolée les unes des autres, car le conteneur est partagé pour l'ensemble du scénario de test. La modification du conteneur peut avoir un impact sur votre autre méthode de test, mais cela ne devrait pas être le cas si vous accédez uniquement au service validator. Cependant, de cette façon, les scénarios de test seront exécutés plus rapidement car vous n’auriez pas besoin d’instancier et de démarrer un nouveau noyau pour chaque méthode de test.

Par souci de référence, nous trouvons l'inspiration pour cette classe dans cet article blog post . Il est écrit en français mais je préfère donner crédit à qui il appartient :) 

Cordialement,
Mat

36
Matt

J'ai bien aimé Kasheens répondre, mais cela ne fonctionne plus pour Symfony 2.3 . Il y a de petits changements:

use Symfony\Component\Validator\Validation;

et

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

Si vous souhaitez valider des annotations, par exemple, utilisez enableAnnotationMapping () comme ci-dessous: 

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

le reste reste le même:

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

Avec Symfony 2.8, il semble que vous puissiez maintenant utiliser la classe AbstractConstraintValidatorTest de cette façon:

<?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);
    }
}

Vous avez un bon échantillon avec la classe IpValidatorTest

5

Réponse (b): Créez le validateur dans le test unitaire (Symfony 2.0)

Si vous avez construit une Constraint et une ConstraintValidator, vous n'avez pas du tout besoin de conteneur d'ID.

Dites par exemple que vous voulez tester la contrainte Type de Symfony et que c'est TypeValidator. Vous pouvez simplement faire ce qui suit:

use Symfony\Component\Validator\Constraints\TypeValidator;
use Symfony\Component\Validator\Constraints\Type;

class TypeValidatorTest extends \PHPUnit_Framework_TestCase
{
  function testIsValid()
  {
    // The Validator class.
    $v = new TypeValidator();

    // Call the isValid() method directly and pass a 
    // configured Type Constraint object (options
    // are passed in an associative array).

    $this->assertTrue($v->isValid(5, new Type(array('type' => 'integer'))));
    $this->assertFalse($v->isValid(5, new Type(array('type' => 'string'))));
  }
}

Avec cela, vous pouvez vérifier chaque validateur que vous voulez avec n'importe quelle configuration de contrainte. Vous n'avez besoin ni de la variable ValidatorFactory ni du noyau Symfony.

Mise à jour: Comme @psylosss l'a souligné, cela ne fonctionne pas dans Symfony 2.5. Cela ne marche pas non plus dans Symfony> = 2.1. L'interface de ConstraintValidator a été modifiée: isValid a été renommé en validate et ne renvoie plus de booléen. Maintenant, vous avez besoin d'une ExecutionContextInterface pour initialiser une ConstraintValidator qui nécessite elle-même au moins une GlobalExecutionContextInterface et une TranslatorInterface... Donc, fondamentalement, ce n'est plus possible sans beaucoup trop de travail.

2
flu

La réponse dans https://stackoverflow.com/a/41884661/4560833 doit être légèrement modifiée pour Symfony 4:

Utilisez ConstraintValidatorTestCase au lieu de AbstractConstraintValidatorTest.

1
floplus

Je ne vois pas de problème avec le WebTestCase. Si vous ne voulez pas de client, n'en créez pas;) Mais si vous utilisez un service éventuellement différent de celui utilisé par votre application, vous risquez de perdre un peu de temps. Donc personnellement, j'ai fait comme ça:

class ProductServiceTest extends Symfony\Bundle\FrameworkBundle\Test\WebTestCase
{

    /**
     * Setup the kernel.
     *
     * @return null
     */
    public function setUp()
    {
        $kernel = self::getKernelClass();

        self::$kernel = new $kernel('dev', true);
        self::$kernel->boot();
    }

    public function testFoo(){
        $em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
        $v  = self::$kernel->getContainer()->get('validator');

        // ...
    }

}

La réponse est moins DRY que Matt - car vous répéterez le code (pour chaque classe de test) et démarrerez souvent le noyau (pour chaque méthode de test), mais il est autonome et ne nécessite aucune dépendance supplémentaire. dépend de vos besoins. De plus, je me suis débarrassé de l'exigence statique.

De plus, vous êtes certain de disposer des mêmes services que ceux utilisés par votre application, pas les services par défaut ni fictifs, car vous démarrez le noyau dans l'environnement que vous souhaitez tester.

0
FMaz008