How can i unit test ContainsItalianVatinValidator
custom validator, but w*ithout accessing the container* and the validator
service (and thus, create a stub object)?
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
// ...
}
}
In my test case i know i should access the ConstraintViolationList
, but i don't know how to do it from the validator itself:
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());
}
}
Updated for 3.4:
I've put the context creation in a trait, so we can reuse it for all our custom constraints.
class SomeConstraintValidatorTest extends TestCase
{
use ConstraintValidationTrait;
/** @var SomeConstraint */
private $constraint;
protected function setUp()
{
parent::setUp();
$this->constraint = new SomeConstraint();
}
public function testValidateOnInvalid()
{
$this->assertConstraintRejects('someInvalidValue', $this->constraint);
}
public function testValidateOnValid()
{
$this->assertConstraintValidates('someValidValue', $this->constraint);
}
}
The trait:
<?php
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContext;
trait ConstraintValidationTrait
{
/**
* The assertion is done in the mock.
*
* @param mixed $value
*/
public function assertConstraintValidates($value, Constraint $constraint): void
{
$validator = $this->createValidator($constraint, true);
$validator->validate($value, $constraint);
}
/**
* The assertion is done in the mock.
*
* @param mixed $value
*/
public function assertConstraintRejects($value, Constraint $constraint): void
{
$validator = $this->createValidator($constraint, false);
$validator->validate($value, $constraint);
}
/** This is the phpunit mock method this trait requires */
abstract protected function createMock($originalClassName): MockObject;
private function createValidator(Constraint $constraint, bool $shouldValidate): ConstraintValidator
{
$context = $this->mockExecutionContext($shouldValidate);
$validatorClass = get_class($constraint) . 'Validator';
/** @var ConstraintValidator $validator */
$validator = new $validatorClass();
$validator->initialize($context);
return $validator;
}
/**
* Configure a SomeConstraintValidator.
*
* @param string|null $expectedMessage The expected message on a validation violation, if any.
*
* @return ExecutionContext
*/
private function mockExecutionContext(bool $shouldValidate): ExecutionContext
{
/** @var ExecutionContext|MockObject $context */
$context = $this->createMock(ExecutionContext::class);
if ($shouldValidate) {
$context->expects($this->never())->method('addViolation');
} else {
$context->expects($this->once())->method('addViolation');
}
return $context;
}
}
When you take a look at the parent class of the validator Symfony\Component\Validator\ConstraintValidator
you see that there is a method called initialize
which takes an instance of Symfony\Component\Validator\ExecutionContext
as argument.
After you created the validator you can call the initialize
method and pass a mock context to the validator. You don't have to test if the addViolation
method works correctly, you only have to test if it is called and if it is called with the correct parameters. You can do that with the mock functionality of 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);
...
In this code you have to replace [message] with the message stored in $constraint->message
.
Actually, this question is more related to PHPUnit than to Symfony. You may find the Test Doubles chapter of the PHPUnit documentation interesting.
Updated for Symfony 2.5+. Add a test for each possible message that the validate()
method in your validator might add with the value that would trigger that message.
<?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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With