java - 自定义 bean 验证器在单元测试中抛出空指针
问题描述
我创建了一个示例自定义约束来探索休眠 bean 验证实现。约束本身相当简单;给定一个特定的字符串和一个枚举,验证器使用正则表达式来检查字符串是否匹配特定的模式(通过 EmpType 枚举选择)。我有以下内容:
员工编号.java
package com.lmig.beanvalidation.domain.customconstraints.employeenumber;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
//implementation of ConstraintValidator interface, i.e. the class that performs custom logic to validate the value
@Constraint(validatedBy = { EmployeeNumberValidator.class})
public @interface EmployeeNumber {
//Validation message for failures
String message() default "{EmployeeNumber.standard}";
//allows "grouping"; can choose under what circumstances this will fire via interfaces
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
EmpType value() default EmpType.STANDARD;
}
EmpType.java
package com.lmig.beanvalidation.domain.customconstraints.employeenumber;
public enum EmpType {
ADMIN,STANDARD;
}
EmployeeNumberValidator.java
package com.lmig.beanvalidation.domain.customconstraints.employeenumber;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
/**
* Backing validator class for EmployeeNumber bean validation annotation
*/
public class EmployeeNumberValidator implements ConstraintValidator<EmployeeNumber, String> {
private static Pattern STANDARD_PATTERN = Pattern.compile("E\\d{6}");
private static Pattern ADMIN_PATTERN = Pattern.compile("A\\d{6}");
protected EmpType empType;
/**
* checks if emp number is not blank, and matches either standard or admin employee number format based on empType
*
* @param empNum String to check for validity
* @param constraintValidatorContext context for validation annotation
*
* @return true if empNum matches empType specific regex, false if empNum is blank or does not match any regex
*/
@Override
public boolean isValid(final String empNum, final ConstraintValidatorContext constraintValidatorContext) {
boolean isValid;
if(StringUtils.isBlank(empNum)) {
isValid = false;
} else if(empType.equals(EmpType.STANDARD)) {
isValid = isStandardNumberValid(empNum);
} else {
isValid = isAdminNumberValid(empNum);
if(!isValid) {
constraintValidatorContext.disableDefaultConstraintViolation();;
constraintValidatorContext
.buildConstraintViolationWithTemplate("{EmployeeNumber.admin}")
.addConstraintViolation();
}
}
return isValid;
}
/**
* Compares empNum against Standard employee number pattern regex
* @param empNum string to compare against standard pattern regex
*
* @return true if match, false otherwise
*/
private boolean isStandardNumberValid(final String empNum) {
final Matcher matcher = STANDARD_PATTERN.matcher(empNum);
return matcher.matches();
}
/**
* Compares empNum against Admin employee number pattern regex
* @param empNum string to compare against admin pattern regex
*
* @return true if match, false otherwise
*/
private boolean isAdminNumberValid(final String empNum) {
final Matcher matcher = ADMIN_PATTERN.matcher(empNum);
return matcher.matches();
}
}
使用以下自定义消息:
ValidationMessages.properties
EmployeeNumber.standard=Standard employee Number must start with E, followed by 6 digits
EmployeeNumber.admin=Admin employee number must start with A, followed by 6 digits
以及以下测试类:
EmployeeNumberValidatorTest
package com.lmig.beanvalidation.domain.customconstraints.employeenumber;
import static com.lmig.beanvalidation.testutils.ValidatorUtils.getErrorMessagesFromSet;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Test;
public class EmployeeNumberValidatorTest {
private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private final Validator validator = factory.getValidator();
@Test
public void test_standardEmployeeNumber_employeeNumberIsValid_noErrorsReturned() {
EmpNumTestClass input = new EmpNumTestClass("E123456", "A123456");
final Set<ConstraintViolation<EmpNumTestClass>> errors = validator.validate(input);
final List<String> actualMessages = getErrorMessagesFromSet(errors);
assertThat(actualMessages).isEmpty();
}
@Test
public void test_standardEmployeeNumber_employeeNumberIsInvalid_standardEmployeeNumberValidationFailure() {
final Set<ConstraintViolation<EmpNumTestClass>> errors = validator.validateValue(EmpNumTestClass.class,
"standardEmpNum", "invalid");
final List<String> actualMessages = getErrorMessagesFromSet(errors);
assertThat(actualMessages).hasSize(1).contains("Standard employee Number must start with E, followed by 6 digits");
}
@Test
public void test_adminEmployeeNumber_employeeNumberIsValid_noErrorsReturned() {
final Set<ConstraintViolation<EmpNumTestClass>> errors = validator.validateValue(EmpNumTestClass.class,
"adminEmpNum", "A123456");
final List<String> actualMessages = getErrorMessagesFromSet(errors);
assertThat(actualMessages).isEmpty();
}
@Test
public void test_adminEmployeeNumber_employeeNumberIsInvalid_adminEmployeeNumberValidationFailure() {
final Set<ConstraintViolation<EmpNumTestClass>> errors = validator.validateValue(EmpNumTestClass.class,
"adminEmpNum", "invalid");
final List<String> actualMessages = getErrorMessagesFromSet(errors);
assertThat(actualMessages).hasSize(1).contains("Admin employee number must start with A, followed by 6 digits");
}
class EmpNumTestClass {
@EmployeeNumber(value = EmpType.STANDARD)
private String standardEmpNum;
@EmployeeNumber(value = EmpType.ADMIN)
private String adminEmpNum;
EmpNumTestClass(final String standardEmpNum, final String adminEmpNum) {
this.standardEmpNum = standardEmpNum;
this.adminEmpNum= adminEmpNum;
}
}
}
当运行任何测试时,如果在 EmployeeNumberValidator 类中的 else if 上出现空指针异常:
javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:177)
at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:68)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:380)
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:169)
at com.lmig.beanvalidation.domain.customconstraints.employeenumber.EmployeeNumberValidatorTest.test_standardEmployeeNumber_employeeNumberIsValid_noErrorsReturned(EmployeeNumberValidatorTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.NullPointerException
at com.lmig.beanvalidation.domain.customconstraints.employeenumber.EmployeeNumberValidator.isValid(EmployeeNumberValidator.java:35)
at com.lmig.beanvalidation.domain.customconstraints.employeenumber.EmployeeNumberValidator.isValid(EmployeeNumberValidator.java:14)
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:171)
... 33 more
我已经在域对象上测试了这个的变体。测试布局与上面类似,但不是在测试中使用内部类,而是使用 src 文件夹树中的一个类,并且运行没有问题。除了使用内部类之外,我找不到测试之间的任何区别(作为尝试测试 EmpType 验证变体的一部分,而不会以不会在生产场景中使用的方式污染或以其他方式操纵 src 域类) . 我已经尝试了几种方法(在测试文件中看到;大多数使用 validateValue,我已经使用填充对象测试了 validateProperty 和标准验证函数)但是对于我使用的所有变体都收到相同的错误。
提到的错误意味着没有设置empType,但我不确定为什么会出现这种情况,因为我已经传入了测试内部类中存在的两个注释的值,并确认我已经尝试传入 EmpType src 类上的值并为其运行测试,并且没有遇到空指针。使用带有这些我还没有读过的注释的内部类是否存在某种约束?我正在使用 hibernate-validator 版本 6.0.14 final
解决方案
推荐阅读
- c# - 如何发送 XML 标签的值
- html - 内联图像的背景图像样式
- java - 您如何获取有关单击对象的数据?
- c++ - 如何隐藏模板基类的参数类型
- c# - C# 问题转换 PHP Hex2bin
- javascript - 在引导表中传递 JSON 数据
- django - AttributeError:模块“blog.views”没有属性“post_detail”
- c# - 构建 c# MVC asp.net api 网页 - 我想使用 index.cshtml 页面但收到失败消息:CS1980(代码说明见文本)
- bash - Shell 脚本:-ne 的意外运算符
- cordova - 如何调试 Ionic 中使用的 SqlLite 数据库?