我的实体有一个自定义的Hibernate Validator。我的一个验证器使用Autowired Spring @Repository。应用程序运行正常,我的验证器上的存储库已成功自动装配。
问题是我无法找到测试验证器的方法,因为我无法在其中注入我的存储库。
Person.class:
@Entity
@Table(schema = "dbo", name = "Person")
@PersonNameMustBeUnique
public class Person {
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@Column()
@NotBlank()
private String name;
//getters and setters
//...
}
PersonNameMustBeUnique.class
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { PersonNameMustBeUniqueValidator.class })
@Documented
public @interface PersonNameMustBeUnique{
String message() default "";
Class<?>[] groups() default {};
Class<? extends javax.validation.Payload>[] payload() default {};
}
验证员:
public class PersonNameMustBeUniqueValidatorimplements ConstraintValidator<PersonNameMustBeUnique, Person> {
@Autowired
private PersonRepository repository;
@Override
public void initialize(PersonNameMustBeUnique constraintAnnotation) { }
@Override
public boolean isValid(Person entidade, ConstraintValidatorContext context) {
if ( entidade == null ) {
return true;
}
context.disableDefaultConstraintViolation();
boolean isValid = nameMustBeUnique(entidade, context);
return isValid;
}
private boolean nameMustBeUnique(Person entidade, ConstraintValidatorContext context) {
//CALL REPOSITORY TO CHECK IF THE NAME IS UNIQUE
//ADD errors if not unique...
}
}
上下文文件有一个验证器bean:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
同样,它工作正常,但我不知道如何测试它。
我的测试文件是:
@RunWith(MockitoJUnitRunner.class)
public class PersonTest {
Person e;
static Validator validator;
@BeforeClass
public static void setUpClass() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void name__must_not_be_null() {
e = new Person();
e.setName(null);
Set<ConstraintViolation<Person>> violations = validator.validate(e);
assertViolacao(violations, "name", "Name must not be null");
}
}
答案 0 :(得分:3)
在@BeforeClass上:
@BeforeClass
public static void setUpClass() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
在测试中,你需要用你的模拟bean替换bean:
myValidator.initialize(null);
BeanValidatorTestUtils.replaceValidatorInContext(validator, usuarioValidoValidator, e);
完成所有魔术的课程:
public class BeanValidatorTestUtils {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <A extends Annotation, E> void replaceValidatorInContext(Validator validator,
final ConstraintValidator<A, ?> validatorInstance,
E instanceToBeValidated) {
final Class<A> anotacaoDoValidador = (Class<A>)
((ParameterizedType) validatorInstance.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0];
ValidationContextBuilder valCtxBuilder = ReflectionTestUtils.<ValidationContextBuilder>invokeMethod(validator,
"getValidationContext");
ValidationContext<E> validationContext = valCtxBuilder.forValidate(instanceToBeValidated);
ConstraintValidatorManager constraintValidatorManager = validationContext.getConstraintValidatorManager();
final ConcurrentHashMap nonSpyHashMap = new ConcurrentHashMap();
ConcurrentHashMap spyHashMap = spy(nonSpyHashMap);
doAnswer(new Answer<Object>() {
@Override public Object answer(InvocationOnMock invocation) throws Throwable {
Object key = invocation.getArguments()[0];
Object keyAnnotation = ReflectionTestUtils.getField(key, "annotation");
if (anotacaoDoValidador.isInstance(keyAnnotation)) {
return validatorInstance;
}
return nonSpyHashMap.get(key);
}
}).when(spyHashMap).get(any());
ReflectionTestUtils.setField(constraintValidatorManager, "constraintValidatorCache", spyHashMap);
}
}
答案 1 :(得分:3)
我面临着非常类似的问题:如何编写具有自动装配的配置bean的自定义验证器的纯单元测试?
我可以通过遵循以下代码(受用户this的abhishekrvce回答启发)解决问题。
这是使用@Autowired配置bean的自定义验证器的纯单元测试,该配置bean从配置文件中读取数据(代码中未显示)。
@Import({MyValidator.class})
@ContextConfiguration(classes = MyConfiguration.class, initializers = ConfigFileApplicationContextInitializer.class)
class MyValidatorTest {
private LocalValidatorFactoryBean validator;
@Autowired
private ConfigurableApplicationContext applicationContext;
@BeforeEach
void initialize() {
SpringConstraintValidatorFactory springConstraintValidatorFactory
= new SpringConstraintValidatorFactory(
applicationContext.getAutowireCapableBeanFactory());
validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(springConstraintValidatorFactory);
validator.setApplicationContext(applicationContext);
validator.afterPropertiesSet();
}
@Test
void isValid()
{
Set<ConstraintViolation<MyObject>> constraintViolations = validator
.validate(myObjectInstance);
assertThat(constraintViolations).hasSize(1);
}
}
答案 2 :(得分:2)
最近我的自定义验证器遇到了同样的问题。我需要验证传递给控制器方法的模型(方法级验证)。调用验证器但无法注入依赖项(@Autowired)。我花了几天时间搜索和调试整个过程。最后,我可以让它发挥作用。我希望我的经验可以节省一些时间给其他人带来同样的问题。这是我的解决方案:
拥有像这样的jsr-303自定义验证器:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD,
ElementType.PARAMETER,
ElementType.TYPE,
ElementType.METHOD,
ElementType.LOCAL_VARIABLE,
ElementType.CONSTRUCTOR,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE })
@Constraint(validatedBy = SampleValidator.class)
public @interface ValidSample {
String message() default "Default sample validation error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SampleValidator implements ConstraintValidator<ValidSample, SampleModel> {
@Autowired
private SampleService service;
public void initialize(ValidSample constraintAnnotation) {
//init
}
public boolean isValid(SampleModel sample, ConstraintValidatorContext context) {
service.doSomething();
return true;
}
}
您应该像这样配置弹簧测试:
@ComponentScan(basePackages = { "your base packages" })
@Configurable
@EnableWebMvc
class SpringTestConfig {
@Autowired
private WebApplicationContext wac;
@Bean
public Validator validator() {
SpringConstraintValidatorFactory scvf = new SpringConstraintValidatorFactory(wac.getAutowireCapableBeanFactory());
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(scvf);
validator.setApplicationContext(wac);
validator.afterPropertiesSet();
return validator;
}
@Bean
public MethodValidationPostProcessor mvpp() {
MethodValidationPostProcessor mvpp = new MethodValidationPostProcessor();
mvpp.setValidatorFactory((ValidatorFactory) validator());
return mvpp;
}
@Bean
SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
}
@WebAppConfiguration
@ContextConfiguration(classes = { SpringTestConfig.class, AnotherConfig.class })
public class ASampleSpringTest extends AbstractTestNGSpringContextTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@BeforeClass
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.build();
}
@Test
public void testSomeMethodInvokingCustomValidation(){
// test implementation
// for example:
mockMvc.perform(post("/url/mapped/to/controller")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json))
.andExpect(status().isOk());
}
}
请注意,这里我使用的是testng,但您可以使用JUnit 4.除了使用@RunWith(SpringJUnit4ClassRunner.class)运行测试并且不扩展AbstractTestNGSpringContextTests之外,整个配置都是相同的。
现在,@ ValidSample可用于自定义注释的@Target()中提到的位置。 注意:如果要在方法级别使用@ValidSample批注(如验证方法参数),则应将类级别注释@Validated放在其方法使用批注的类中,控制器或服务类上的示例。
答案 3 :(得分:2)
我们还面临类似的问题,即ConstrainValidator类中的@Autowiring失败(未初始化)。我们的ConstraintValidator Implemented类使用的值应该从application.yml
文件中读取。下面的解决方案帮助我们,因为它使用的是纯弹簧镜。希望这可以通过适当的SpringJunit4ClassRunner帮助。
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import org.springframework.web.context.WebApplicationContext;
@WebAppConfiguration
@ContextConfiguration(classes = {ApplicationConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {
"spring.someConfigValue.InApplicationYaml=Value1",
})
public class MyTest {
@Autowired
private WebApplicationContext webApplicationContext;
LocalValidatorFactoryBean validator;
@Before
public void setup() {
SpringConstraintValidatorFactory springConstraintValidatorFactory
= new SpringConstraintValidatorFactory(webApplicationContext.getAutowireCapableBeanFactory());
validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(springConstraintValidatorFactory);
validator.setApplicationContext(webApplicationContext);
validator.afterPropertiesSet();
}
@Test
public void should_have_no_violations_for_all_valid_fields() {
Set<ConstraintViolation<PojoClassWhichHaveConstraintValidationAnnotation>> violations = validator.validate(pojoClassObjectWhichHaveConstraintValidationAnnotation);
assertTrue(violations.isEmpty());
}
}
@Configuration
public class ApplicationConfig {
@Value("${spring.someConfigValue.InApplicationYaml=Value1}")
public String configValueToBeReadFromApplicationYamlFile;
}
答案 4 :(得分:1)
Spring Boot 2允许毫不费力地将Bean注入自定义的Validator中。Spring框架自动检测实现ConstraintValidator
接口的所有类,实例化它们并连接所有依赖项。
我遇到了类似的问题,这就是我的实现方式。
步骤1界面
@Documented
@Constraint(validatedBy = UniqueFieldValidator.class)
@Target({ ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueField {
String message() default "Duplicate Name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
第2步验证器
public class UniqueFieldValidator implements ConstraintValidator<UniqueField, Person> {
@Autowired
PersionList personRepository;
private static final Logger log = LoggerFactory.getLogger(PersonRepository.class);
@Override
public boolean isValid(Person object, ConstraintValidatorContext context) {
log.info("Validating Person for Duplicate {}",object);
return personRepository.isPresent(object);
}
}
用法
@Component
@Validated
public class PersonService {
@Autowired
PersionList personRepository;
public void addPerson(@UniqueField Person person) {
personRepository.add(person);
}
}
答案 5 :(得分:1)
使用JUnit4和Mockito的解决方案:
@Import(LocalValidatorFactoryBean.class)
@RunWith(SpringRunner.class)
public class MyCustomValidatorTest {
@Autowired
private Validator validator;
@MockBean
private PersonRepository repository;
@Test
public void name_must_not_be_null() {
// given
when(repository.findByName(any())).thenReturn(Collection.emptyList());
Person person = new Person();
person.setName(null);
// when
Set<ConstraintViolation<Person>> violations = validator.validate(person);
// then
assertViolation(violations, "name", "Name must not be null");
}
}
答案 6 :(得分:1)
您可以单独测试验证器并使用反射来注入自动装配属性。
约束注解:
@Target({ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailAlreadyExistsValidator.class)
public @interface EmailAlreadyExists {
String message() default "Email already exists in the database";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
验证器:
public class EmailAlreadyExistsValidator implements
ConstraintValidator<EmailAlreadyExists, String> {
@Autowired
private UserRepository repository;
@Override
public void initialize(EmailAlreadyExists constraintAnnotation) {}
public boolean isValid(String email, ConstraintValidatorContext context) {
Optional<User> opUser = repository.findByEmail(email);
return (opUser.isEmpty());
}
}
单元测试(ReflectionTestUtils 发挥作用):
public class EmailAlreadyExistsValidatorTest {
@Mock
private EmailAlreadyExists emailAlreadyExists;
@Mock
private ConstraintValidatorContext constraintValidatorContext;
@Mock
private UserRepository repository;
private EmailAlreadyExistsValidator validator;
@BeforeEach
public void beforeEach() {
MockitoAnnotations.openMocks(this);
validator = new EmailAlreadyExistsValidator();
validator.initialize(emailAlreadyExists);
ReflectionTestUtils.setField(validator, "repository", repository);
}
@Test
@DisplayName("Given an user with existent email then validation must fail")
public void isValid_existentPassword_mustFail() {
final String existentEmail = "testuser@test.com";
User savedUser = new User("1213443455",
"Test User",
existentEmail,
"12345",
new Date());
Optional<User> opUser = Optional.of(savedUser);
when(repository.findByEmail(anyString())).thenReturn(opUser);
assertFalse(validator.isValid(existentEmail,constraintValidatorContext));
}
}
答案 7 :(得分:0)
您可以在测试中将以下bean添加到您的Spring Context中:
ask_price_target_good
答案 8 :(得分:0)
可能有点晚了,但我最近遇到了同样的问题,所以我会发布我是如何解决这个问题的,因为这可以帮助其他人。
问题基本上是您通过调用 Validator
获得的 Hibernate 标准 Validation.buildDefaultValidatorFactory().getValidator()
实现对 Spring 的应用程序上下文一无所知,因此它无法在您的自定义约束验证器中注入依赖项。
在 Spring 应用程序中,Validator
和 ValidatorFactory
接口的实现是类 LocalValidatorFactoryBean
,它可以委托给 ApplicationContext
以实例化具有依赖项的约束验证器注入。
你需要做的是
ValidatorFactory
,其中包含项目符号 1 中的所有约束验证器Validator
这是自定义验证器工厂
public class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintValidator<?, ?>> customConstraintValidators;
public CustomLocalValidatorFactoryBean(List<ConstraintValidator<?, ?>> customConstraintValidators) {
this.customConstraintValidators = customConstraintValidators;
setProviderClass(HibernateValidator.class);
afterPropertiesSet();
}
@Override
protected void postProcessConfiguration(Configuration<?> configuration) {
super.postProcessConfiguration(configuration);
ConstraintValidatorFactory defaultConstraintValidatorFactory =
configuration.getDefaultConstraintValidatorFactory();
configuration.constraintValidatorFactory(
new ConstraintValidatorFactory() {
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
for (ConstraintValidator<?, ?> constraintValidator : customConstraintValidators) {
if (key.equals(constraintValidator.getClass())) //noinspection unchecked
return (T) constraintValidator;
}
return defaultConstraintValidatorFactory.getInstance(key);
}
@Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
defaultConstraintValidatorFactory
.releaseInstance(instance);
}
}
);
}
}
然后在您的测试课程中,您只需执行以下操作:
class MyTestSuite {
private final PersonRepository mockPersonRepository = Mockito.mock(PersonRepository.class);
private final List<ConstraintValidator<?,?>> customConstraintValidators =
Collections.singletonList(new PersonNameMustBeUniqueValidator(mockPersonRepository));
private final ValidatorFactory customValidatorFactory =
new CustomLocalValidatorFactoryBean(customConstraintValidators);
private final Validator validator = customValidatorFactory.getValidator();
@Test
void myTestCase() {
// mock the dependency: Mockito.when(mockPersonRepository...)
Person p = new Person();
//setters omitted
Set<ConstraintViolation<?>> violations = validator.validate(p);
//assertions on the set of constraint violations
}
}
希望有所帮助。您可以查看我的这篇文章了解更多详情:https://codemadeclear.com/index.php/2021/01/26/how-to-mock-dependencies-when-unit-testing-custom-validators/
答案 9 :(得分:0)
我通过在我的 UnitTests 中覆盖默认的 Hibernate ConstraintValidatorFactory 来实现
LocalValidatorFactoryBean localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setProviderClass(HibernateValidator.class);
localValidatorFactory.setConstraintValidatorFactory(new ConstraintValidatorFactoryImpl() {
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> arg0) {
T ret = super.getInstance(arg0);
if (ret instanceof UniqueEmailValidator) {
((UniqueEmailValidator) ret).setUserService(userService);
}
return ret;
}
});
localValidatorFactory.afterPropertiesSet();