Skip to content

Commit 9cbca1a

Browse files
henry2778wouterj
authored andcommitted
[Validator] Unit Tests
1 parent 4900291 commit 9cbca1a

File tree

1 file changed

+246
-23
lines changed

1 file changed

+246
-23
lines changed

validation/custom_constraint.rst

+246-23
Original file line numberDiff line numberDiff line change
@@ -194,22 +194,112 @@ Class Constraint Validator
194194
~~~~~~~~~~~~~~~~~~~~~~~~~~
195195

196196
Besides validating a single property, a constraint can have an entire class
197-
as its scope. You only need to add this to the ``Constraint`` class::
197+
as its scope. Consider the following classes, that describe the receipt of some payment::
198198

199-
public function getTargets()
199+
// src/AppBundle/Model/PaymentReceipt.php
200+
class PaymentReceipt
200201
{
201-
return self::CLASS_CONSTRAINT;
202+
/**
203+
* @var User
204+
*/
205+
private $user;
206+
207+
/**
208+
* @var array
209+
*/
210+
private $payload;
211+
212+
public function __construct(User $user, array $payload)
213+
{
214+
$this->user = $user;
215+
$this->payload = $payload;
216+
}
217+
218+
public function getUser(): User
219+
{
220+
return $this->user;
221+
}
222+
223+
public function getPayload(): array
224+
{
225+
return $this->payload;
226+
}
227+
}
228+
229+
// src/AppBundle/Model/User.php
230+
231+
class User
232+
{
233+
/**
234+
* @var string
235+
*/
236+
private $email;
237+
238+
public function __construct($email)
239+
{
240+
$this->email = $email;
241+
}
242+
243+
public function getEmail(): string
244+
{
245+
return $this->email;
246+
}
247+
}
248+
249+
As an example you're going to check if the email in receipt payload matches the user email.
250+
To validate the receipt, it is required to create the constraint first.
251+
You only need to add the ``getTargets()`` method to the ``Constraint`` class::
252+
253+
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceipt.php
254+
namespace AppBundle\Validator\Constraints;
255+
256+
use Symfony\Component\Validator\Constraint;
257+
258+
/**
259+
* @Annotation
260+
*/
261+
class ConfirmedPaymentReceipt extends Constraint
262+
{
263+
public $userDoesntMatchMessage = 'User email does not match the receipt email';
264+
265+
public function getTargets()
266+
{
267+
return self::CLASS_CONSTRAINT;
268+
}
202269
}
203270

204271
With this, the validator's ``validate()`` method gets an object as its first argument::
205272

206-
class ProtocolClassValidator extends ConstraintValidator
273+
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceiptValidator.php
274+
namespace AppBundle\Validator\Constraints;
275+
276+
use Symfony\Component\Validator\Constraint;
277+
use Symfony\Component\Validator\ConstraintValidator;
278+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
279+
280+
class ConfirmedPaymentReceiptValidator extends ConstraintValidator
207281
{
208-
public function validate($protocol, Constraint $constraint)
282+
/**
283+
* @param PaymentReceipt $receipt
284+
* @param Constraint|ConfirmedPaymentReceipt $constraint
285+
*/
286+
public function validate($receipt, Constraint $constraint)
209287
{
210-
if ($protocol->getFoo() != $protocol->getBar()) {
211-
$this->context->buildViolation($constraint->message)
212-
->atPath('foo')
288+
if (!$receipt instanceof PaymentReceipt) {
289+
throw new UnexpectedValueException($receipt, PaymentReceipt::class);
290+
}
291+
292+
if (!$constraint instanceof ConfirmedPaymentReceipt) {
293+
throw new UnexpectedValueException($constraint, ConfirmedPaymentReceipt::class);
294+
}
295+
296+
$receiptEmail = $receipt->getPayload()['email'] ?? null;
297+
$userEmail = $receipt->getUser()->getEmail();
298+
299+
if ($userEmail !== $receiptEmail) {
300+
$this->context
301+
->buildViolation($constraint->userDoesntMatchMessage)
302+
->atPath('user.email')
213303
->addViolation();
214304
}
215305
}
@@ -232,47 +322,46 @@ not to the property:
232322
namespace App\Entity;
233323
234324
use App\Validator as AcmeAssert;
235-
325+
236326
/**
237-
* @AcmeAssert\ProtocolClass
327+
* @AppAssert\ConfirmedPaymentReceipt
238328
*/
239-
class AcmeEntity
329+
class PaymentReceipt
240330
{
241331
// ...
242332
}
243333
244334
.. code-block:: yaml
245335
246-
# config/validator/validation.yaml
247-
App\Entity\AcmeEntity:
336+
# src/AppBundle/Resources/config/validation.yml
337+
AppBundle\Model\PaymentReceipt:
248338
constraints:
249-
- App\Validator\ProtocolClass: ~
339+
- AppBundle\Validator\Constraints\ConfirmedPaymentReceipt: ~
250340
251341
.. code-block:: xml
252342
253-
<!-- config/validator/validation.xml -->
254-
<class name="App\Entity\AcmeEntity">
255-
<constraint name="App\Validator\ProtocolClass"/>
343+
<!-- src/AppBundle/Resources/config/validation.xml -->
344+
<class name="AppBundle\Model\PaymentReceipt">
345+
<constraint name="AppBundle\Validator\Constraints\ConfirmedPaymentReceipt"/>
256346
</class>
257347
258348
.. code-block:: php
259349
260-
// src/Entity/AcmeEntity.php
261-
namespace App\Entity;
262-
263-
use App\Validator\ProtocolClass;
350+
// src/AppBundle/Model/PaymentReceipt.php
351+
use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt;
264352
use Symfony\Component\Validator\Mapping\ClassMetadata;
265353
266-
class AcmeEntity
354+
class PaymentReceipt
267355
{
268356
// ...
269357
270358
public static function loadValidatorMetadata(ClassMetadata $metadata)
271359
{
272-
$metadata->addConstraint(new ProtocolClass());
360+
$metadata->addConstraint(new ConfirmedPaymentReceipt());
273361
}
274362
}
275363
364+
<<<<<<< HEAD
276365
Testing Custom Constraints
277366
--------------------------
278367

@@ -315,3 +404,137 @@ unit tests for your custom constraints::
315404
// ...
316405
}
317406
}
407+
408+
How to Unit Test your Validator
409+
-------------------------------
410+
411+
To create a unit test for you custom validator, your test case class should
412+
extend the ``ConstraintValidatorTestCase`` class and implement the ``createValidator()`` method::
413+
414+
protected function createValidator()
415+
{
416+
return new ContainsAlphanumericValidator();
417+
}
418+
419+
After that you can add any test cases you need to cover the validation logic::
420+
421+
use AppBundle\Validator\Constraints\ContainsAlphanumeric;
422+
use AppBundle\Validator\Constraints\ContainsAlphanumericValidator;
423+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
424+
425+
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
426+
{
427+
protected function createValidator()
428+
{
429+
return new ContainsAlphanumericValidator();
430+
}
431+
432+
/**
433+
* @dataProvider getValidStrings
434+
*/
435+
public function testValidStrings($string)
436+
{
437+
$this->validator->validate($string, new ContainsAlphanumeric());
438+
439+
$this->assertNoViolation();
440+
}
441+
442+
public function getValidStrings()
443+
{
444+
return [
445+
['Fabien'],
446+
['SymfonyIsGreat'],
447+
['HelloWorld123'],
448+
];
449+
}
450+
451+
/**
452+
* @dataProvider getInvalidStrings
453+
*/
454+
public function testInvalidStrings($string)
455+
{
456+
$constraint = new ContainsAlphanumeric([
457+
'message' => 'myMessage',
458+
]);
459+
460+
$this->validator->validate($string, $constraint);
461+
462+
$this->buildViolation('myMessage')
463+
->setParameter('{{ string }}', $string)
464+
->assertRaised();
465+
}
466+
467+
public function getInvalidStrings()
468+
{
469+
return [
470+
['example_'],
471+
['@$^&'],
472+
['hello-world'],
473+
['<body>'],
474+
];
475+
}
476+
}
477+
478+
You can also use the ``ConstraintValidatorTestCase`` class for creating test cases for class constraints::
479+
480+
use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt;
481+
use AppBundle\Validator\Constraints\ConfirmedPaymentReceiptValidator;
482+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
483+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
484+
485+
class ConfirmedPaymentReceiptValidatorTest extends ConstraintValidatorTestCase
486+
{
487+
protected function createValidator()
488+
{
489+
return new ConfirmedPaymentReceiptValidator();
490+
}
491+
492+
public function testValidReceipt()
493+
{
494+
$receipt = new PaymentReceipt(new User('[email protected]'), ['email' => '[email protected]', 'data' => 'baz']);
495+
$this->validator->validate($receipt, new ConfirmedPaymentReceipt());
496+
497+
$this->assertNoViolation();
498+
}
499+
500+
/**
501+
* @dataProvider getInvalidReceipts
502+
*/
503+
public function testInvalidReceipt($paymentReceipt)
504+
{
505+
$this->validator->validate(
506+
$paymentReceipt,
507+
new ConfirmedPaymentReceipt(['userDoesntMatchMessage' => 'myMessage'])
508+
);
509+
510+
$this->buildViolation('myMessage')
511+
->atPath('property.path.user.email')
512+
->assertRaised();
513+
}
514+
515+
public function getInvalidReceipts()
516+
{
517+
return [
518+
[new PaymentReceipt(new User('[email protected]'), [])],
519+
[new PaymentReceipt(new User('[email protected]'), ['email' => '[email protected]'])],
520+
];
521+
}
522+
523+
/**
524+
* @dataProvider getUnexpectedArguments
525+
*/
526+
public function testUnexpectedArguments($value, $constraint)
527+
{
528+
self::expectException(UnexpectedValueException::class);
529+
530+
$this->validator->validate($value, $constraint);
531+
}
532+
533+
public function getUnexpectedArguments()
534+
{
535+
return [
536+
[new \stdClass(), new ConfirmedPaymentReceipt()],
537+
[new PaymentReceipt(new User('[email protected]'), []), new Unique()],
538+
];
539+
}
540+
}

0 commit comments

Comments
 (0)