Maintenant nous allons voir comment tester un validateur. Notre objectif est de créer une nouvelle contrainte de validation que l'on va pouvoir utiliser sur l'ensemble de nos entités. Il est donc important de nous assurer que ce système de validation fonctionne convenablement avant de l'intégrer au reste de l'application.

Lorsque l'on crée de nouvelles contraintes de validation on va se retrouver avec deux classes séparées. La contrainte d'un côté et la validation de l'autre

Tester la contrainte

La contrainte permet de déclarer les paramètres associés à notre règle et il est important de tester qu'ils soient convenablement définis.
On peut ainsi écrire quelques tests unitaires afin de nous assurer que les différentes options passées au constructeur sont vérifiées.

<?php

namespace App\Tests\Validator;

use App\Validator\EmailDomain;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\MissingOptionsException;

class EmailDomainTest extends TestCase
{

    public function testRequiredParameters () {
        $this->expectException(MissingOptionsException::class);
        new EmailDomain();
    }

    public function testBadShapedBlockedParameter () {
        $this->expectException(ConstraintDefinitionException::class);
        new EmailDomain(['blocked' => 'azeaze']);
    }

    public function testOptionIsSetAsProperty () {
        $arr = ['a', 'b'];
        $domain = new EmailDomain(['blocked' => $arr]);
        $this->assertEquals($arr, $domain->blocked);
    }

}

Tester le validateur

Pour tester le validateur on va aussi pouvoir utiliser le système de tests unitaires. Il faudra en revanche injecter un mock de la classe ExecutionContextInterface afin de vérifier si notre classe génère les violations convenablement. De la même manière on injectera des mocks au niveau du constructeur pour les différentes dépendances (comme un repository par exemple).

<?php

namespace App\Tests\Validator;

use App\Repository\ConfigRepository;
use App\Validator\EmailDomain;
use App\Validator\EmailDomainValidator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;

class EmailDomainValidatorTest extends TestCase
{

    public function testCatchBadDomains()
    {
        $constraint = new EmailDomain([
            'blocked' => ['baddomain.fr', 'aze.com']
        ]);
        $this->getValidator(true)->validate('demo@baddomain.fr', $constraint);
    }

    public function testAcceptGoodDomains()
    {
        $constraint = new EmailDomain([
            'blocked' => ['baddomain.fr', 'aze.com']
        ]);
        $this->getValidator(false)->validate('demo@gooddomain.fr', $constraint);
    }

    public function testBlockedDomainFromDatabase()
    {
        $constraint = new EmailDomain([
            'blocked' => ['baddomain.fr', 'aze.com']
        ]);
        $this->getValidator(true, ['baddbdomain.fr'])->validate('demo@baddbdomain.fr', $constraint);
    }

    /**
     * Instancie un validateur pour nos tests
     **/
    private function getValidator($expectedViolation = false, $dbBlockedDomain = [])
    {
        $repository = $this->getMockBuilder(ConfigRepository::class)
            ->disableOriginalConstructor()
            ->getMock();

        $repository->expects($this->any())
            ->method('getAsArray')
            ->with('blocked_domains')
            ->willReturn($dbBlockedDomain);

        $validator = new EmailDomainValidator($repository);
        $context = $this->getContext($expectedViolation);
        $validator->initialize($context);
        return $validator;
    }

    /**
     * Génère un contexte qui attend (ou non) une violation 
     **/
    private function getContext(bool $expectedViolation): ExecutionContextInterface
    {
        $context = $this->getMockBuilder(ExecutionContextInterface::class)->getMock();
        if ($expectedViolation) {
            $violation = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)->getMock();
            $violation->expects($this->any())->method('setParameter')->willReturn($violation);
            $violation->expects($this->once())->method('addViolation');
            $context
                ->expects($this->once())
                ->method('buildViolation')
                ->willReturn($violation);
        } else {
            $context
                ->expects($this->never())
                ->method('buildViolation');
        }
        return $context;
    }

}