gre*_*emo 13 phpunit symfony symfony-2.1
我如何单元测试ContainsItalianVatinValidator自定义验证器,但w*不访问容器*和validator服务(从而创建存根对象)?
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
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
在我的测试用例中,我知道我应该访问ConstraintViolationList,但我不知道如何从验证器本身做到这一点:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
Flo*_*fer 23
当您查看验证器的父类时,Symfony\Component\Validator\ConstraintValidator您会看到有一个方法被调用initialize,该方法将一个实例Symfony\Component\Validator\ExecutionContext作为参数.
创建验证程序后,您可以调用该initialize方法并将模拟上下文传递给验证程序.您不必测试addViolation方法是否正常工作,您只需要测试它是否被调用以及是否使用正确的参数调用它.您可以使用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);
...
Run Code Online (Sandbox Code Playgroud)
在此代码中,您必须将[message]替换为存储的消息$constraint->message.
实际上,这个问题与PHPUnit有关,而与Symfony有关.您可能会发现PHPUnit文档的Test Doubles章节很有趣.
iis*_*ael 13
针对Symfony 2.5+进行了更新.为validate()验证器中的方法可能添加的每个可能消息添加测试,其中包含将触发该消息的值.
<?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);
}
}
Run Code Online (Sandbox Code Playgroud)
更新为 3.4:
我已将上下文创建放入一个特征中,因此我们可以将其重用于所有自定义约束。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
特点:
<?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;
}
}
Run Code Online (Sandbox Code Playgroud)