-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Validator] Unit Tests #13898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
[Validator] Unit Tests #13898
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -179,22 +179,112 @@ Class Constraint Validator | |
~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Besides validating a single property, a constraint can have an entire class | ||
as its scope. You only need to add this to the ``Constraint`` class:: | ||
as its scope. Consider the following classes, that describe the receipt of some payment:: | ||
|
||
public function getTargets() | ||
// src/AppBundle/Model/PaymentReceipt.php | ||
class PaymentReceipt | ||
{ | ||
return self::CLASS_CONSTRAINT; | ||
/** | ||
* @var User | ||
*/ | ||
private $user; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private $payload; | ||
|
||
public function __construct(User $user, array $payload) | ||
{ | ||
$this->user = $user; | ||
$this->payload = $payload; | ||
} | ||
|
||
public function getUser(): User | ||
{ | ||
return $this->user; | ||
} | ||
|
||
public function getPayload(): array | ||
{ | ||
return $this->payload; | ||
} | ||
} | ||
|
||
// src/AppBundle/Model/User.php | ||
|
||
class User | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private $email; | ||
|
||
public function __construct($email) | ||
{ | ||
$this->email = $email; | ||
} | ||
|
||
public function getEmail(): string | ||
{ | ||
return $this->email; | ||
} | ||
} | ||
|
||
As an example you're going to check if the email in receipt payload matches the user email. | ||
To validate the receipt, it is required to create the constraint first. | ||
You only need to add the ``getTargets()`` method to the ``Constraint`` class:: | ||
|
||
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceipt.php | ||
namespace AppBundle\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
|
||
/** | ||
* @Annotation | ||
*/ | ||
class ConfirmedPaymentReceipt extends Constraint | ||
{ | ||
public $userDoesntMatchMessage = 'User email does not match the receipt email'; | ||
|
||
public function getTargets() | ||
{ | ||
return self::CLASS_CONSTRAINT; | ||
} | ||
} | ||
|
||
With this, the validator's ``validate()`` method gets an object as its first argument:: | ||
|
||
class ProtocolClassValidator extends ConstraintValidator | ||
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceiptValidator.php | ||
namespace AppBundle\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
|
||
class ConfirmedPaymentReceiptValidator extends ConstraintValidator | ||
{ | ||
public function validate($protocol, Constraint $constraint) | ||
/** | ||
* @param PaymentReceipt $receipt | ||
* @param Constraint|ConfirmedPaymentReceipt $constraint | ||
*/ | ||
public function validate($receipt, Constraint $constraint) | ||
{ | ||
if ($protocol->getFoo() != $protocol->getBar()) { | ||
$this->context->buildViolation($constraint->message) | ||
->atPath('foo') | ||
if (!$receipt instanceof PaymentReceipt) { | ||
throw new UnexpectedValueException($receipt, PaymentReceipt::class); | ||
} | ||
|
||
if (!$constraint instanceof ConfirmedPaymentReceipt) { | ||
throw new UnexpectedValueException($constraint, ConfirmedPaymentReceipt::class); | ||
} | ||
|
||
$receiptEmail = $receipt->getPayload()['email'] ?? null; | ||
$userEmail = $receipt->getUser()->getEmail(); | ||
|
||
if ($userEmail !== $receiptEmail) { | ||
$this->context | ||
->buildViolation($constraint->userDoesntMatchMessage) | ||
->atPath('user.email') | ||
->addViolation(); | ||
} | ||
} | ||
|
@@ -214,39 +304,173 @@ not to the property: | |
.. code-block:: php-annotations | ||
|
||
/** | ||
* @AcmeAssert\ProtocolClass | ||
* @AppAssert\ConfirmedPaymentReceipt | ||
*/ | ||
class AcmeEntity | ||
class PaymentReceipt | ||
{ | ||
// ... | ||
} | ||
|
||
.. code-block:: yaml | ||
|
||
# src/AppBundle/Resources/config/validation.yml | ||
AppBundle\Entity\AcmeEntity: | ||
AppBundle\Model\PaymentReceipt: | ||
constraints: | ||
- AppBundle\Validator\Constraints\ProtocolClass: ~ | ||
- AppBundle\Validator\Constraints\ConfirmedPaymentReceipt: ~ | ||
|
||
.. code-block:: xml | ||
|
||
<!-- src/AppBundle/Resources/config/validation.xml --> | ||
<class name="AppBundle\Entity\AcmeEntity"> | ||
<constraint name="AppBundle\Validator\Constraints\ProtocolClass"/> | ||
<class name="AppBundle\Model\PaymentReceipt"> | ||
<constraint name="AppBundle\Validator\Constraints\ConfirmedPaymentReceipt"/> | ||
</class> | ||
|
||
.. code-block:: php | ||
|
||
// src/AppBundle/Entity/AcmeEntity.php | ||
use AppBundle\Validator\Constraints\ProtocolClass; | ||
// src/AppBundle/Model/PaymentReceipt.php | ||
use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt; | ||
use Symfony\Component\Validator\Mapping\ClassMetadata; | ||
|
||
class AcmeEntity | ||
class PaymentReceipt | ||
{ | ||
// ... | ||
|
||
public static function loadValidatorMetadata(ClassMetadata $metadata) | ||
{ | ||
$metadata->addConstraint(new ProtocolClass()); | ||
$metadata->addConstraint(new ConfirmedPaymentReceipt()); | ||
} | ||
} | ||
|
||
How to Unit Test your Validator | ||
------------------------------- | ||
|
||
To create a unit test for you custom validator, your test case class should | ||
extend the ``ConstraintValidatorTestCase`` class and implement the ``createValidator()`` method:: | ||
|
||
protected function createValidator() | ||
{ | ||
return new ContainsAlphanumericValidator(); | ||
} | ||
|
||
After that you can add any test cases you need to cover the validation logic:: | ||
|
||
use AppBundle\Validator\Constraints\ContainsAlphanumeric; | ||
use AppBundle\Validator\Constraints\ContainsAlphanumericValidator; | ||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; | ||
|
||
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase | ||
{ | ||
protected function createValidator() | ||
{ | ||
return new ContainsAlphanumericValidator(); | ||
} | ||
|
||
/** | ||
* @dataProvider getValidStrings | ||
*/ | ||
public function testValidStrings($string) | ||
{ | ||
$this->validator->validate($string, new ContainsAlphanumeric()); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
public function getValidStrings() | ||
{ | ||
return [ | ||
['Fabien'], | ||
['SymfonyIsGreat'], | ||
['HelloWorld123'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidStrings | ||
*/ | ||
public function testInvalidStrings($string) | ||
{ | ||
$constraint = new ContainsAlphanumeric([ | ||
'message' => 'myMessage', | ||
]); | ||
|
||
$this->validator->validate($string, $constraint); | ||
|
||
$this->buildViolation('myMessage') | ||
->setParameter('{{ string }}', $string) | ||
->assertRaised(); | ||
} | ||
|
||
public function getInvalidStrings() | ||
{ | ||
return [ | ||
['example_'], | ||
['@$^&'], | ||
['hello-world'], | ||
['<body>'], | ||
]; | ||
} | ||
} | ||
|
||
You can also use the ``ConstraintValidatorTestCase`` class for creating test cases for class constraints:: | ||
|
||
use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt; | ||
use AppBundle\Validator\Constraints\ConfirmedPaymentReceiptValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; | ||
|
||
class ConfirmedPaymentReceiptValidatorTest extends ConstraintValidatorTestCase | ||
{ | ||
protected function createValidator() | ||
{ | ||
return new ConfirmedPaymentReceiptValidator(); | ||
} | ||
|
||
public function testValidReceipt() | ||
{ | ||
$receipt = new PaymentReceipt(new User('[email protected]'), ['email' => '[email protected]', 'data' => 'baz']); | ||
$this->validator->validate($receipt, new ConfirmedPaymentReceipt()); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidReceipts | ||
*/ | ||
public function testInvalidReceipt($paymentReceipt) | ||
{ | ||
$this->validator->validate( | ||
$paymentReceipt, | ||
new ConfirmedPaymentReceipt(['userDoesntMatchMessage' => 'myMessage']) | ||
); | ||
|
||
$this->buildViolation('myMessage') | ||
->atPath('property.path.user.email') | ||
->assertRaised(); | ||
} | ||
|
||
public function getInvalidReceipts() | ||
{ | ||
return [ | ||
[new PaymentReceipt(new User('[email protected]'), [])], | ||
[new PaymentReceipt(new User('[email protected]'), ['email' => '[email protected]'])], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider getUnexpectedArguments | ||
*/ | ||
public function testUnexpectedArguments($value, $constraint) | ||
{ | ||
self::expectException(UnexpectedValueException::class); | ||
|
||
$this->validator->validate($value, $constraint); | ||
} | ||
|
||
public function getUnexpectedArguments() | ||
{ | ||
return [ | ||
[new \stdClass(), new ConfirmedPaymentReceipt()], | ||
[new PaymentReceipt(new User('[email protected]'), []), new Unique()], | ||
]; | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using the
ProtocolClassValidator
here that we implemented above?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I see, the
ProtocolClassValidator
is more like 'sketchy' snippet, no real logic in there. Ergo I am not sure that we can produce some significant unit test example for it. Maybe we should improve theProtocolClassValidator
snippet? @xabbuhThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would work for me too. As long as we use a consistent example throughout this document the experience when reading the article will be better IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@henry2778 it would be very great if you could use (and enhance) the
ProtocolClassValidator
to stay consistent across the document. Thanks