javax.validation.ValidationException: HV000028: непредвиденное исключение во время isValid в тесте - PullRequest
0 голосов
/ 14 февраля 2020

Я не могу понять, как написать модульный тест для пользовательского средства проверки ограничений. У меня есть следующий класс Validator:

@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class SchoolIdValidator implements ConstraintValidator<ValidSchoolId, String> {

    private SchoolService schoolService;

    @Override
    public void initialize(ValidSchoolId constraintAnnotation) {}

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return schoolService.isValidSchoolId(s);
    }
}

Validator используется следующей аннотацией:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SchoolIdValidator.class)
public @interface ValidSchoolId {

    String message() default "Must be a valid school. Found: ${validatedValue}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}

SchoolService - это класс, который вызывает Api School с использованием ApiClient, который класс Generi c с Generi c restTemplate запросов:

@Service
@AllArgsConstructor
public class SchoolService {

    private ApiClient apiSchoolClient;

    public Boolean isValidSchoolId(String schoolId){
        try {
            ResponseEntity res = apiSchoolClient.get(
                    String.format("/stores/%s", schoolId), Object.class, null);
            return res.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException e) {
            if(e.getStatusCode().value() == 404)
                return false;
            else
                throw new TechnicalException("Can't get response from school Api");
        } catch(RestClientException e) {
            throw new TechnicalException("Can't get response from School Api");
        }
    }
}

И, наконец, класс Student:

@Data
public class Student {

    private String id;
    private String firstName;
    private String lastName;
    @ValidSchoolId
    private String schoolId;
}

Когда я запускаю приложение, оно работает отлично. Но он совсем не работает со следующим классом Test:

@RunWith(MockitoJUnitRunner.class)
@ExtendWith(SpringExtension.class)
public class SchoolIdValidatorTest {

    Validator validator;

    @InjectMocks
    private SchoolService schoolService;

    @Mock
    private ApiClient apiSchoolClient;

    @Before
    public void setUp() throws Exception {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
        MockitoAnnotations.initMocks(this);
    }


    @Test
    public void isValid_SchoolIdExists_ShouldValidate() {

        Mockito.when(this.apiSchoolClient.get(Mockito.eq("/schools/12"), Mockito.any(), Mockito.any()))
                .thenReturn(new ResponseEntity<>(HttpStatus.OK));
        //given
        Student student = new Student();
        simulation.setSchoolId("12");
        //when
        Set<ConstraintViolation<Student>> violations = validator.validate(student);

        //then
        assertEquals(0, violations.size());
    }

Когда он входит, SchoolIdValidator schoolService всегда null. Таким образом, он всегда выбрасывает NPE на SchoolIdValidator.isValid. Если у кого-то есть решение, оно будет очень полезно.

Спасибо за чтение и вашу помощь.

1 Ответ

1 голос
/ 14 февраля 2020

Вероятно, проблема @InjectMocks.

@RunWith(MockitoJUnitRunner.class)
@ExtendWith(SpringExtension.class)
public class SchoolIdValidatorTest {

    Validator validator;

    @InjectMocks
    private SchoolService schoolService;

вместе с

Validation.buildDefaultValidatorFactory().getValidator()

Я предполагаю, что SchoolService обычно является bean-компонентом, управляемым Spring, и SchoolIdValidator вводит его. Используя @InjectMocks, вы говорите, что это не управляемый компонент Spring. Этот класс создается Mockito, поэтому он никогда не будет внедрен в ваш валидатор.

Я не уверен, какое здесь правильное решение, поскольку вы не публикуете какие-либо классы конфигурации. Скорее всего, вам нужен дополнительный класс конфигурации теста, который создает экземпляр вашего валидатора ApiClient и внедряет ваш SchoolService. Вы также можете просто смоделировать ApiClient с помощью @MockBean, что сделает его управляемым компонентом Spring, но также позволит имитировать.

В некотором роде, у вас здесь есть конструкторы. Нет причин использовать @InjectMocks. Просто используйте new SchoolService(apiSchoolClient).

...