Symfony自定义@Assert\Amount验证器功能测试如何设置$currency属性值
自定义依赖实体其他字段的Assert验证器功能测试方案
核心实现思路
- 带
propertyPath配置的验证器不需要在测试时单独给约束实例传依赖字段值,直接初始化对应实体实例,同时给被校验字段、依赖字段赋值后,将整个实体传入验证流程即可,和普通单字段验证器测试的差异仅在于需要提前设置校验上下文的根对象。 - 原理是Symfony验证组件解析
propertyPath参数时,会自动从当前校验上下文的根对象(即你定义的Entity实例)中按路径读取对应属性值,不需要手动给约束类传currency相关参数。 - 提前确认自定义的Amount验证器内部正确接入了属性访问器逻辑:校验触发时通过属性访问器从根对象读取
propertyPath指向的字段值,再执行对应金额规则判断,逻辑和官方约束的propertyPath解析逻辑保持一致即可。
可参考的测试代码示例
<?php namespace App\Tests\Validator; use App\Validator\Amount; use App\Validator\AmountValidator; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; class AmountValidatorTest extends ConstraintValidatorTestCase { protected function createValidator() { // 若你的Amount验证器依赖属性访问器组件,直接在此处注入即可 return new AmountValidator(PropertyAccess::createPropertyAccessor()); } /** * 测试不同币种下符合规则的金额场景 * @dataProvider validCaseProvider */ public function testValidAmount(string $currency, float $amount) { // 初始化实体,同时给两个关联字段赋值 $testEntity = new class { protected $currency; protected $amount; public function setCurrency(string $currency): void { $this->currency = $currency; } public function getCurrency(): string { return $this->currency; } public function setAmount(float $amount): void { $this->amount = $amount; } public function getAmount(): float { return $this->amount; } }; $testEntity->setCurrency($currency); $testEntity->setAmount($amount); // 关键步骤:设置校验上下文的根对象、当前校验的属性路径,否则无法读取到currency字段值 $this->setObject($testEntity); $this->setPropertyPath('amount'); // 触发校验 $this->validator->validate($amount, new Amount([ 'propertyPath' => 'currency' ])); // 断言无校验错误 $this->assertNoViolation(); } /** * 测试不同币种下不符合规则的金额场景 * @dataProvider invalidCaseProvider */ public function testInvalidAmount(string $currency, float $invalidAmount, string $expectMessage) { $testEntity = new class { protected $currency; protected $amount; public function setCurrency(string $currency): void { $this->currency = $currency; } public function getCurrency(): string { return $this->currency; } public function setAmount(float $amount): void { $this->amount = $amount; } public function getAmount(): float { return $this->amount; } }; $testEntity->setCurrency($currency); $testEntity->setAmount($invalidAmount); $this->setObject($testEntity); $this->setPropertyPath('amount'); $constraint = new Amount(['propertyPath' => 'currency']); $this->validator->validate($invalidAmount, $constraint); // 断言抛出预期的校验错误 $this->buildViolation($expectMessage) ->setParameter('{{ value }}', $invalidAmount) ->assertRaised(); } public function validCaseProvider(): array { return [ 'CNY允许两位小数' => ['CNY', 99.99], 'JPY不允许小数' => ['JPY', 100.0], ]; } public function invalidCaseProvider(): array { return [ 'CNY传三位小数触发错误' => ['CNY', 99.999, '人民币金额最多支持2位小数'], 'JPY传小数位触发错误' => ['JPY', 100.5, '日元金额不支持小数部分'], ]; } }
注意:如果之前写单字段验证器测试时没有调用
setObject()方法,这里必须补上该步骤,否则验证器内部无法获取到根实体实例,会导致propertyPath解析失败,这是这类跨字段依赖验证器测试最常见的坑。
内容的提问来源于stack exchange,提问作者Bademeister
相关产品推荐
相关产品推荐

