Unit testing custom validation constraint in Symfony 2.1 but without accessing the container?

gremo picture gremo · Sep 13, 2012 · Viewed 8.6k times · Source

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

Answer

Florian Eckerstorfer picture Florian Eckerstorfer · Sep 13, 2012

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.