I'm currently providing coverage - testing the validation of my DTO through MockMVC request call. I recently introduced a new field in my Registration ConstraintValidator, supportedSpecializations, of which I inject the values from application.properties for easy maintenance and expandability. see code fragment below:
@Component
public class RegistrationValidator implements ConstraintValidator<Registration, String> {
//campus.students.supportedspecializations="J2E,.NET,OracleDB,MySQL,Angular"
@Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private String specializationExceptionMessage;
//All ExceptionMessages are maintained in a separate class
@Override
public void initialize(Registration constraintAnnotation) {
exceptionMessage = constraintAnnotation.regionException().getMessage();
}
@Override
public boolean isValid(RegistrationData regData, ConstraintValidatorContext context) {
String[] specializations = supportedSpecializations.split(",");
boolean isValidSpecialization = Arrays.stream(specializations)
.anyMatch(spec -> spec.equalsIgnoreCase(regData.getSpec()));
if (!isValidSpecialization){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(specializationExceptionMessage)
.addConstraintViolation();
return false;
}
//additional validation logic...
return true;
}
}
Unit tests now fail due to the field not being injected by the defined property of the @Value annotation. I'm not sure if ReflectionTestUtils could help in my case, so any suggestions are greatly appreciated about how to inject the required values in UnitTests.
Spring version is 2.1.0 I'm currently testing with the following snippet:
@InjectMocks
private StudentController mockRestController;
@Mock
private StudentService mockStudentService;
@Mock
private ValidationExceptionTranslator mockExceptionTranslator;
@Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
@Test
public void testValidation_UnsupportedSpecialization() throws Exception {
MvcResult mvcResult = mockMvc.perform(
post("/Students").contentType(MediaType.APPLICATION_JSON_UTF8).content(
"{\"registrationData\":{\"spec\":\"unsupported\"}}"))
.andExpect(status().isBadRequest())
.andReturn();
assertEquals(VALIDATION_FAILED, mvcResult.getResponse().getContentAsString());
verify(mockExceptionTranslator, times(1)).translate(Mockito.any());
verify(mockStudentService, times(0)).insertStudent(Mockito.any());
}
I tried annotating my Test class with @RunWith(SpringRunner.class) and @SpringBootTest(classes = Application.class), but the validation test still fails due to @Value not being resolved. I might be wrong but I think the ConstraintValidator's instance is created before we reach the restController, so a MockMVC perform(...) call couldn't simply just make sure the appropriate @Value in the validator gets injected into supportedSpecializations.
Solved the issue the following way: Added the following annotations to the Test Class
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
Then autowired the controller and mockMVC, finally annotated service and translator with Spring's @MockBean
So currently it looks something like this:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class StudentValidatorTest {
@Autowired
private StudentController mockRestController;
@MockBean
private StudentService mockStudentService;
@MockBean
private ValidationExceptionTranslator mockExceptionTranslator;
@Autowired
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
//...and tests...