복붙노트

[SPRING] Autowired spring 서비스로 맞춤형 검사기 테스트

SPRING

Autowired spring 서비스로 맞춤형 검사기 테스트

내 엔터티에 대한 사용자 지정 최대 절전 유효성 검사기가 있습니다. 내 유효성 검사기 중 하나는 Autowired Spring @Repository를 사용합니다. 응용 프로그램이 잘 작동하고 저장소가 유효성 검사기에서 자동으로 Autowired됩니다.

문제는 내 유효성 검사기를 테스트 할 수있는 방법을 찾을 수 없기 때문에 내부에 저장소를 삽입 할 수 없기 때문입니다.

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");
    }

}

해결법

  1. ==============================

    1.@BeforeClass에서 :

    @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);
        }
    
    }
    
  2. ==============================

    2.최근에 나는 사용자 정의 유효성 검사기에 동일한 문제가있었습니다. 컨트롤러의 메서드 (메서드 수준 유효성 검사)에 전달되는 모델의 유효성을 검사해야했습니다. 유효성 검사기가 호출되었지만 종속성 (@Autowired)을 삽입 할 수 없습니다. 전체 프로세스를 검색하고 디버깅하는 데는 며칠이 걸렸습니다. 마지막으로, 나는 그것을 작동하게 할 수 있습니다. 나는 내 경험이 같은 문제를 가진 다른 사람들을 위해 시간을 절약하길 바란다. 여기 내 해결책은 다음과 같습니다.

    최근에 나는 사용자 정의 유효성 검사기에 동일한 문제가있었습니다. 컨트롤러의 메서드 (메서드 수준 유효성 검사)에 전달되는 모델의 유효성을 검사해야했습니다. 유효성 검사기가 호출되었지만 종속성 (@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. ==============================

    3.Spring Framework는 ConstraintValidator 인터페이스를 구현하고 인스턴스화하고 모든 종속성을 연결하는 모든 클래스를 자동으로 감지합니다.

    Spring Framework는 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);
        }
    }
    
  4. from https://stackoverflow.com/questions/25610061/test-custom-validator-with-autowired-spring-service by cc-by-sa and MIT license