Tuto #3 : test unitaire sur les contraintes de validation des « entity »

Ici nous allons voir comment tester l’ensemble des contraintes de validation d’une entity. Avec ce que nous allons mettre en place nous allons pouvoir tester les asserts présent dans votre entité. Ce tuto s’adresse à ceux qui utilisent les annotations pour définir les règles de validation de leur entité. Mais vous pouvez modifier le code pour l’adapter à vos besoins.

L’arborescence de notre projet est la suivante :

– app
– src
– – AppBundle
– – – Entity
– – – – Installation.php
– tests
– – – AppBundle
– – – – Entity
– – – – – InstallationTest.php
– – – AbstractEntityValidation.php
– – – AbstractWebTestCase.php
– vendor
– web

Pré-requis :

  • Symfony (Nous utilisons la version 3.4)
  • Avoir installer et configurer symfony/phpunit-bridge. Voir https://symfony.com/doc/3.4/testing.html

1 – Création des classes abstraites.

Nous allons créer deux classes abstraites. La première est « AbstractWebTestCase » et la seconde est « AbstractEntityValidation ».

– AbstractWebTestCase : toute classe de test peut hériter cette classe abstraite afin de profiter de ces fonctions, notament avoir accès au container.
– AbstractEntityValidation : il s’agit d’une classe abstraite spécifique qui apporte des fonctions pour les tests sur les contraintes de validation.

AbstractWebTestCase :


<?php

namespace Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * AbstractWebTestCase
 * @author Hubsine 
 */
abstract class AbstractWebTestCase extends WebTestCase
{
    /**
     * Container
     * 
     * @var ContainerInterface
     */
    protected $container;

    public function __construct($name = null, array $data = array(), $dataName = '') 
    {
        parent::__construct($name, $data, $dataName);
        
        self::bootKernel($data);
        
        $this->container    = static::$kernel->getContainer();
    }
    
    /**
     * Get Container
     * 
     * @return ContainerInterface A ContainerInterface instance
     */
    protected function getContainer()
    {
        return $this->container;
    }
    
    /**
     * Get a service
     * 
     * @param string $id The service identifier
     * 
     * @return object The associated service
     */
    protected function get($id)
    {
        return $this->container->get($id);
    }
}

AbstractEntityValidation :


<?php

namespace Tests;

use Tests\AbstractWebTestCase;
use ReflectionProperty;

/**
 * AbstractEntityValidation
 *
 * @author Hubsine <contact@hubsine.com>
 */
abstract class AbstractEntityValidation extends AbstractWebTestCase
{
    /**
     * Get a valid object from which variations will be built
     *
     * @return object
     */
    abstract protected function getValidObject();

    /**
     * Get sets of values which, when applied to an otherwise valid object, will not cause a violation.
     * e.g. return [ 'name' => ['Richard', 'Bob'], 'password' => ['tibbsIsFluffy', 'hfdkd'], 'confirmPassword' => ['tibbsIsFluffy', 'hfdkd'] ];
     *
     * @return array
     */
    abstract protected function getValidValueSets();

    /**
     * Get sets of values which, when applied to an otherwise valid object, will not cause a violation.
     * e.g. return [ 'name' => ['', ' ', null], 'password' => ['myCatIsFluffy', 'hgjfdhfkdf'], 'confirmPassword' => ['myCatIsFurry', 'hgjfdhfkdf'] };
     *
     * @return array
     */
    abstract protected function getInvalidValueSets();

    /**
     * @return array of [validation_group,validation_group]
     * for use when entities need validation_groups
     */
    protected function getValidationGroups()
    {
        return [];
    }

    ###
    # DataProvider
    ###
    
    final public function provideValidDataForEntityValidationTest()
    {
        foreach ($this->getValidValueSets() as $propertyName => $validValues) 
        {
            foreach ($validValues as $testDescription => $valueSet) 
            { 
                yield [$testDescription => [$propertyName => $valueSet]];
            }
        }
    }

    final public function provideInvalidDataForEntityValidationTest()
    {
        
        foreach ($this->getInvalidValueSets() as $propertyName => $invalidValues) 
        {
            foreach ($invalidValues as $key => $valueSet) 
            {                
                yield [$key => [$propertyName => $valueSet]];
            }
        }
    }
    
    ###
    # Tests to run
    ###
    
    /**
     * @dataProvider provideValidDataForEntityValidationTest
     * @param array $validValues
     */
    final public function testValidForEntityValidationTest(array $validValues)
    {
        $object = $this->getValidObject();
        
        foreach ($validValues as $property => $value) 
        {
            $this->setProperty($object, $property, $value);
        }
       
        $this->assertValidates($object);
    }

    /**
     * @dataProvider provideInvalidDataForEntityValidationTest
     * @param array $invalidValues
     */
    final public function testInvalidForEntityValidationTest(array $invalidValues)
    {
        $object = $this->getValidObject();
        
        foreach ($invalidValues as $property => $value) 
        {
            $this->setProperty($object, $property, $value);
        }
        
        $this->assertGreaterThanOrEqual(
            1,    
            count($this->validate($object))
        );
    }

    /**
     * Validation for valid data 
     * 
     * @param mixed $object
     */
    private function assertValidates($object)
    {
        $violations = $this->validate($object);
        $messages   = [];
        
        foreach ($violations as $violation) 
        {
            $messages[] = $violation . PHP_EOL . "Invalid Value: " . var_export($violation->getInvalidValue(), true);
        }
        
        $this->assertEquals(0, count($violations), implode(PHP_EOL . PHP_EOL, $messages));
    }

    /**
     * Set object property
     * 
     * @param mixed $object
     * @param string $property
     * @param value $value
     */
    private function setProperty($object, $property, $value)
    {
        $reflectionProperty = new ReflectionProperty(get_class($object), $property);
        $reflectionProperty->setAccessible(true);
        $reflectionProperty->setValue($object, $value);
    }

    /**
     * Validate a object
     * 
     * @param mixed $object
     * 
     * @return \Symfony\Component\Validator\ConstraintViolationListInterface
     */
    protected function validate($object)
    {
        $constraintViolationListe = $this->get('validator')->validate($object, null, $this->getValidationGroups());
        
        return $constraintViolationListe;
    }
}

2 – Création de la classe de test


<?php

namespace Tests\AppBundle\Entity;

use Tests\AbstractEntityValidation;
use AppBundle\Entity\Installation as InstallationEntity;

/**
 * InstallationTest
 *
 * @author Hubsine <contact@hubsine.com>
 */
class InstallationTest extends AbstractEntityValidation
{
    protected function getValidObject()
    {
        return $installation = new InstallationEntity();
    }

    /**
     * {@inheritdoc}
     */
    protected function getValidValueSets()
    {
        return [
            'hosting'  => [InstallationEntity::getHostings()[0]] // key 0 = shared => is okey
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function getInvalidValueSets()
    {
        return [
            'hosting' => [null, '', ' ', 'test', InstallationEntity::getHostings()[1]] // Key 1 = vps => not oke because vps entry already exist in database
        ];
    }
}

3 – Création de l’entité avec les assertions


<?php 
namespace AppBundle\Entity; 

use Doctrine\ORM\Mapping as ORM; 
use Gedmo\Mapping\Annotation as Gedmo; 
use Symfony\Component\Validator\Constraints as Assert; 
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 

/** 
* Installation 
* 
* @ORM\Table() 
* 
* @UniqueEntity(fields="hosting", message="installation.hosting.unique_entity") 
*/ 
class Installation { 

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="hosting", type="string", length=30, unique=true)
     * 
     * @assert\NotBlank(message="installation.hosting.not_blank")
     * @Assert\Choice(callback="getHostings", strict=true, message="installation.hosting.choice")
     */
    private $hosting;

    public static function getHostings()
    {
        return ['shared', 'vps'];
    }
    
    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set hosting
     *
     * @param string $hosting
     *
     * @return Installation
     */
    public function setHosting($hosting)
    {
        $this->hosting = $hosting;

        return $this;
    }

    /**
     * Get hosting
     *
     * @return string
     */
    public function getHosting()
    {
        return $this->hosting;
    }
}

4 – Lancer votre test

Avec la commande suivante vous pouvez lancer vos tests :

./vendor/bin/simple-phpunit

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *