2012-09-12 13 views
10

W jaki sposób można przetestować niestandardowy weryfikator urządzenia, ale w * bez dostępu do kontenera * i usługi (a tym samym utworzyć obiekt pośredniczący)?Jednostka testująca niestandardowe ograniczenie sprawdzania poprawności w Symfony 2.1, ale bez dostępu do kontenera?

class ContainsItalianVatinValidator extends ConstraintValidator 
{ 
    /** 
    * @param mixed $value 
    * @param \Symfony\Component\Validator\Constraint $constraint 
    */ 
    public function validate($value, Constraint $constraint) 
    {  
     if (!preg_match('/^[0-9]{11}$/', $value, $matches)) { 
      $this->context->addViolation($constraint->message, array(
       '%string%' => $value 
      )); 
     } 

     // Compute and check control code 
     // ... 
    } 
} 

W moim przypadku testu wiem, że powinny uzyskać dostęp do ConstraintViolationList, ale nie wiem jak to zrobić od samego walidator:

class ContainsItalianVatinValidatorTest extends \PHPUnit_Framework_TestCase 
{ 
    public function testEmptyItalianVatin() 
    { 
     $emptyVatin = ''; 
     $validator = new ContainsItalianVatinValidator(); 
     $constraint = new ContainsItalianVatinConstraint(); 

     // Do the validation 
     $validator->validate($emptyVatin, $constraint); 

     // How can a get a violation list and call ->count()? 
     $violations = /* ... */; 

     // Assert 
     $this->assertGreaterThan(0, $violations->count()); 
    } 
} 
+0

Wyodrębniłbym logikę walidacji do usługi i napisał test jednostkowy dla tej usługi. Wewnątrz klasy sprawdzania poprawności sprawdzasz ograniczenie za pomocą usługi i dodajesz komunikat, jeśli sprawdzanie poprawności nie powiedzie się. Tym samym twoja logika walidacji nie jest powiązana z ramą i jest bardziej odporna na przyszłe zmiany. – fabwu

Odpowiedz

20

Gdy spojrzeć Pod klasa nadrzędna walidatora Symfony\Component\Validator\ConstraintValidator widzisz, że istnieje metoda o nazwie initialize, która przyjmuje instancję Symfony\Component\Validator\ExecutionContext jako argument.

Po utworzeniu walidatora można wywołać metodę initialize i przekazać fałszywy kontekst do walidatora. Nie musisz sprawdzać, czy metoda addViolation działa poprawnie, wystarczy przetestować, czy jest wywoływana i czy jest wywoływana z poprawnymi parametrami. Możesz to zrobić dzięki fałszywej funkcjonalności PHPUnit.

... 
$validator = new ContainsItalianVatinValidator(); 
$context = $this->getMockBuilder('Symfony\Component\Validator\ExecutionContext')-> disableOriginalConstructor()->getMock(); 

$context->expects($this->once()) 
    ->method('addViolation') 
    ->with($this->equalTo('[message]'), $this->equalTo(array('%string%', ''))); 

$validator->initialize($context); 

$validator->validate($emptyVatin, $constraint); 
... 

W tym kodzie trzeba zastąpić [wiadomość] z wiadomością przechowywaną w $constraint->message.

Właściwie to pytanie jest bardziej związane z PHPUnit niż z Symfony. Może Cię zainteresować rozdział Test Doubles dokumentacji PHPUnit.

+0

Niesamowite, dobre wyjaśnienie. Jedyne, czego nie mogę uzyskać, to dlaczego, według ciebie, zliczanie naruszeń jest złe i dlaczego wolę polegać na samym komunikacie ograniczającym. W każdym razie +1. – gremo

+0

Dlaczego należy liczyć naruszenia. Przynajmniej w kodzie w twoim pytaniu jest tylko jedno wołanie do "addViolation". Jeśli ta metoda zostanie wywołana jeden raz, do kontekstu zostanie dodane jedno naruszenie (testy jednostkowe testu Symfony2). –

+0

Jeśli w kodzie powinno być więcej wywołań "addViolation", możesz dodać wiele instrukcji '$ context-> expect', w których każdy obejmuje jedno inne wywołanie' addViolation'. Niestety PHPUnit oferuje tylko dwie metody zliczania liczby wywołań metody 'once' oraz' any'. Jednak [Mockery] (https://github.com/padraic/mockery) jest fałszywą biblioteką, która jest kompatybilna z PHPUnit i może policzyć liczbę wywołań metod na pozorowanym obiekcie. –

10

Zaktualizowano dla Symfony 2.5+. Dodaj test dla każdej możliwej wiadomości, którą metoda validate() w walidatorze może dodać z wartością, która wyzwalałaby ten komunikat.

<?php 

namespace AcmeBundle\Tests\Validator\Constraints; 

use AcmeBundle\Validator\Constraints\SomeConstraint; 
use AcmeBundle\Validator\Constraints\SomeConstraintValidator; 

/** 
* Exercises SomeConstraintValidator. 
*/ 
class SomeConstraintValidatorTest extends \PHPUnit_Framework_TestCase 
{ 
    /** 
    * Configure a SomeConstraintValidator. 
    * 
    * @param string $expectedMessage The expected message on a validation violation, if any. 
    * 
    * @return AcmeBundle\Validator\Constraints\SomeConstraintValidator 
    */ 
    public function configureValidator($expectedMessage = null) 
    { 
     // mock the violation builder 
     $builder = $this->getMockBuilder('Symfony\Component\Validator\Violation\ConstraintViolationBuilder') 
      ->disableOriginalConstructor() 
      ->setMethods(array('addViolation')) 
      ->getMock() 
     ; 

     // mock the validator context 
     $context = $this->getMockBuilder('Symfony\Component\Validator\Context\ExecutionContext') 
      ->disableOriginalConstructor() 
      ->setMethods(array('buildViolation')) 
      ->getMock() 
     ; 

     if ($expectedMessage) { 
      $builder->expects($this->once()) 
       ->method('addViolation') 
      ; 

      $context->expects($this->once()) 
       ->method('buildViolation') 
       ->with($this->equalTo($expectedMessage)) 
       ->will($this->returnValue($builder)) 
      ; 
     } 
     else { 
      $context->expects($this->never()) 
       ->method('buildViolation') 
      ; 
     } 

     // initialize the validator with the mocked context 
     $validator = new SomeConstraintValidator(); 
     $validator->initialize($context); 

     // return the SomeConstraintValidator 
     return $validator; 
    } 

    /** 
    * Verify a constraint message is triggered when value is invalid. 
    */ 
    public function testValidateOnInvalid() 
    { 
     $constraint = new SomeConstraint(); 
     $validator = $this->configureValidator($constraint->someInvalidMessage); 

     $validator->validate('someInvalidValue', $constraint); 
    } 

    /** 
    * Verify no constraint message is triggered when value is valid. 
    */ 
    public function testValidateOnValid() 
    { 
     $constraint = new SomeConstraint(); 
     $validator = $this->configureValidator(); 

     $validator->validate('someValidValue', $constraint); 
    } 
} 
+0

Co za wspaniała odpowiedź! Dziękuję Ci bardzo! – cezar

Powiązane problemy