I can't figure out how to write a Unit Test for a Custom Constraint Validator. I have the following Validator class :
@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);
}
}
The Validator is used by the following annotation :
@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
is a class which calls the School Api by using ApiClient which is a Generic class with Generic restTemplate requests :
@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");
}
}
}
And finally the Student class :
@Data
public class Student {
private String id;
private String firstName;
private String lastName;
@ValidSchoolId
private String schoolId;
}
When I run the app it works perfectly. But, it doesn't work at all with the following Test class :
@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());
}
When it comes in SchoolIdValidator
schoolService
is always null
. So, it always throws a NPE on SchoolIdValidator.isValid. If somebody has a solution it will be really helpful.
Thx for reading and for your help.
The @InjectMocks
here is likely the issue.
@RunWith(MockitoJUnitRunner.class)
@ExtendWith(SpringExtension.class)
public class SchoolIdValidatorTest {
Validator validator;
@InjectMocks
private SchoolService schoolService;
along with
Validation.buildDefaultValidatorFactory().getValidator()
I'm under the assumption that SchoolService
is typically a Spring managed bean, and SchoolIdValidator
would have it injected. By using @InjectMocks
you are saying this isn't a Spring managed bean. This class is instantiated by Mockito, so it would never be injected to your validator.
I'm not sure what the correct solution here is, since you don't post any configuration classes. Most likely, you need an additional test configuration class that instantiates your validator, ApiClient
and injects your SchoolService
. You can also just mock ApiClient
with @MockBean
which will make it a Spring managed bean, but also allow mocking.
Somewhat related, you do have constructors here. There's no reason to use @InjectMocks
. Just use new SchoolService(apiSchoolClient)
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.