Symfony自定义验证器单元测试void断言与上下文Mock报错解决
问题解答
1. void返回类型的validate方法使用assertEmpty断言是否正确?
- 不正确。
validate方法声明返回类型为void,执行后不会产生有效返回值,PHP中调用void方法得到的永远是null,assertEmpty(null)无论验证逻辑是否正确都会通过,这个断言完全没有实际校验效果。 - 合法值场景的正确校验逻辑是:验证执行全程没有调用上下文的违规构建方法,也就是没有产生任何校验错误。
2. 非法数据场景测试报错原因与正确实现
你遇到Error: Call to a member function buildViolation() on null的核心原因有两个:
- 你虽然创建了
ExecutionContextInterface的mock对象,但没有将其注入到验证器实例中。ConstraintValidator基类的$context属性不是通过构造函数注入的,Symfony验证组件在运行时会调用验证器的initialize(ExecutionContextInterface $context)方法完成上下文赋值,你没有调用该方法,$this->context自然是null。 - 你对
buildViolation、addViolation的调用链mock错误:buildViolation方法返回的是ConstraintViolationBuilderInterface实例,addViolation是在该builder实例上调用的,不是直接在context对象上调用。
修正后的测试代码参考
首先是合法手机号场景的测试:
<?php // 别忘了引入对应的类,补全use语句 use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; public function testValidatorWithValidPhone():void { $phone = "+359883202020"; $translationMock = $this->createMock(TranslatorInterface::class); // 合法场景下buildViolation方法绝对不能被调用 $contextMock = $this->createMock(ExecutionContextInterface::class); $contextMock->expects($this->never()) ->method('buildViolation'); $constraint = new ContainsUnsupportedSymbols(); $constraintValidator = new ContainsUnsupportedSymbolsValidator($translationMock); // 注入上下文,这是之前缺失的关键步骤 $constraintValidator->initialize($contextMock); // 执行验证即可,mock的预期规则会自动校验调用行为,不需要断言返回值 $constraintValidator->validate($phone, $constraint); }
非法手机号场景的测试:
public function testValidatorWithInvalidPhone():void { $phone = "+359()883202020"; $testTranslatedMsg = '手机号包含不支持的字符'; // 对应翻译后的错误提示 $translationMock = $this->createMock(TranslatorInterface::class); // 断言翻译方法被正确调用 $translationMock->expects($this->once()) ->method('trans') ->with($constraint->message, ['{phone}' => $phone]) ->willReturn($testTranslatedMsg); // mock违规构建器,断言addViolation被调用 $violationBuilderMock = $this->createMock(ConstraintViolationBuilderInterface::class); $violationBuilderMock->expects($this->once()) ->method('addViolation'); $contextMock = $this->createMock(ExecutionContextInterface::class); // 断言buildViolation被调用,且传入正确的错误信息,返回mock的构建器 $contextMock->expects($this->once()) ->method('buildViolation') ->with($testTranslatedMsg) ->willReturn($violationBuilderMock); $constraint = new ContainsUnsupportedSymbols(); $constraintValidator = new ContainsUnsupportedSymbolsValidator($translationMock); $constraintValidator->initialize($contextMock); $constraintValidator->validate($phone, $constraint); }
补充建议
你还可以补充几个边界场景的测试,覆盖验证器的全部分支逻辑:
- 传入
null或空字符串时,验证器直接返回,不触发任何违规 - 传入非字符串类型值时,抛出
UnexpectedValueException - 传入错误类型的Constraint实例时,抛出
UnexpectedTypeException
内容的提问来源于stack exchange,提问作者Toma Tomov
相关产品推荐
相关产品推荐

