Unit testing of a Symfony custom constraint
Published on 2022-11-05 • Modified on 2022-11-05
This snippet shows how to unit test a custom Symfony validation constraint. One learns every day; I made this snippet inspired by other web resources, then I read the documentation, and I realized there is now a ConstraintValidatorTestCase
which I didn't know yet and never used. This snippet is still OK; at least we see how to use mocks. So, in the following snippet, we'll see how to do the same thing using the ConstraintValidatorTestCase
. The constraint I use for the example is just a dummy one, and it only checks that a string is different from the "coil" value. With these unit tests, I get 100% coverage on the custom constraint.
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Validator;
use App\Entity\Article;
use App\Validator\NotCoil;
use App\Validator\NotCoilValidator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContext;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
final class NotCoilTest extends TestCase
{
/**
* You can put this method in a trait.
*/
private function initValidator(ConstraintValidator $validator, ?string $expectedMessage = null): void
{
$builder = $this->getMockBuilder(ConstraintViolationBuilder::class)
->disableOriginalConstructor()
->getMock();
$context = $this->getMockBuilder(ExecutionContext::class)
->disableOriginalConstructor()
->getMock();
if ($expectedMessage === null) {
// no violation expected
$context->expects(self::never())->method('buildViolation');
} else {
// a violation is expected
$builder->expects(self::once())->method('addViolation');
$context->expects(self::once())
->method('buildViolation')
->with(self::equalTo($expectedMessage))
->willReturn($builder);
}
/* @var ExecutionContext $context */
$validator->initialize($context);
}
/**
* Nominal cases, no validation error.
*/
public function testNotCoilValidatorSuccess(): void
{
$constraint = new NotCoil();
$validator = new NotCoilValidator();
$this->initValidator($validator);
$validator->validate('Foobar', $constraint);
$validator->validate('', $constraint);
}
/**
* Nominal case, a violation is raised.
*/
public function testNotCoilValidatorViolations(): void
{
$constraint = new NotCoil();
$validator = new NotCoilValidator();
$this->initValidator($validator, 'The value should not be COil');
$validator->validate('COil', $constraint);
}
/**
* Value type failure.
*/
public function testNotCoilValidatorUnexpectedValueException(): void
{
$constraint = new NotCoil();
$validator = new NotCoilValidator();
$this->expectException(UnexpectedValueException::class);
$validator->validate(new Article(), $constraint); // not the good value type!
}
/**
* Constraint type failure.
*/
public function testNotCoilValidatorUnexpectedTypeException(): void
{
$constraint = new NotCoil();
$validator = new NotCoilValidator();
$this->expectException(UnexpectedTypeException::class);
$validator->validate($constraint, new Length(['max' => 5])); // not the good constraint!
}
}
More on Stackoverflow Read the doc Random snippet