Spring-Boot UnitTest: #Value in ConstraintValidator - spring-boot

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...

Yes,
use ReflectionTestUtil.
Use ReflectionTestUtil.setField to set the value of supportedSpecializationsin the
setup() method (junit < 1.4)
or in the #Before annotated method (junit > 1.4) in your unit test.
More Details
I recommend against using MockMVC for your unit tests;
it is fine for integration tests,
just not unit tests.
There is no need to start Spring for a Unit Test;
you never need Spring to perform injections for your unit tests.
Instead,
instantiate the class you are testing and call the methods directly.
Here is a simple example:
public class TestRegistrationValidator
{
private static final String VALUE_EXCEPTION_MESSAGE = "VALUE_EXCEPTION_MESSAGE";
private static final String VALUE_SUPPORTED_SPECIALIZATIONS = "BLAMMY,KAPOW";
private RegistrationValidator classToTest;
#Mock
private Registration mockRegistration;
#Mock
private RegionExceptionType mockRegionExceptionType; // use the actual type of regionExcpeption.
#Before
public void preTestSetup()
{
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(classToTest, "supportedSpecializations", VALUE_SUPPORTED_SPECIALIZATIONS);
doReturn(VALUE_EXCEPTION_MESSAGE).when(mockRegionExceptionType).getMessage();
doReturn(mockRegionExceptionType).when(mockRegion).regionException();
}
#Test
public void initialize_allGood_success()
{
classToTest.initialize(mockRegistration);
...assert some stuff.
...perhaps verify some stuff.
}
}

The best option i think for you is to use constructor injection in your RegistrationValidator.class , so that you can assign mock or test values directly for testing as well when required. Example :
#Component
class ExampleClass {
final String text
// Use #Autowired to get #Value to work.
#Autowired
ExampleClass(
// Refer to configuration property
// app.message.text to set value for
// constructor argument message.
#Value('${app.message.text}') final String text) {
this.text = text
}
}
This way you can set your mock values to the variables for unit testing.
Yes, you are right a custom constructor is not an option here, then you could introduce a configuration class where you have these values read from yml or property and autowire that in the validator .That should work for you.
Or
You can provide the #Value properties in a separate test.yml or test.properties and specify that to be taken up while running your integrated tests. In that case you should be able to resolve these values.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ExampleApplication.class)
#TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests {
}
The #TestPropertySource annotation has higher precedence order and it should resolve your values.

Related

Why is spring.profiles.active set in test class not available in environment property of the actual class under test?

Will I be able access enviorment.getProperty("spring.profiles.active") in the class that I am trying to test by setting #ActiveProfiles on Test class,
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(value = TradeController.class)
#ActiveProfiles("test)
class TradeControllerTest{
#Autowired
private MockMvc mockMvc;
#MockBean
private TradeServiceImpl tradeServiceImpl;
#Mock
private ConfigurableEnvironment enviroment;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test() throws Exception{
Mockito.doNothing().when(tradeServiceImpl).getAllTrades(any());
RequestBuilder requestBuilder = MockMvcRequestBuilder.get(new
URI("/getTrades")).accept(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn(); // calling the
actual class under test
assertEquals(result.getResponse().getStatus(), 400);
}
}
Below is the class, where I am reading spring.profiles.active. It is however, coming null
#RestController
public class TradeController{
#Autowired
private TradeServiceImpl tradeServiceImpl;
#Autowired
private Environment environment;
#GetMapping("/getTrade")
public ResponseEntity<String> getTrade(#PathVariable final String
tradeId){
String profile = environment.getProperty("spring.profiles.active");
}
}
You cannot access the active profile with environment.getProperty("spring.profiles.active"), there is the specific Environment#getActiveProfiles method that returns the set of profiles explicitly made active for this environment. So if you autowired your ApplicationContext ctx in your class you can obtain the set of active profiles like below :
Environment env = ctx.getEnvironment();
String[] profiles = env.getActiveProfiles();

Spring boot rest api mockito + mockmvc persistence

I would like to create Test for my rest controller:
#Controller
#RequestMapping("/v2/api/show/project")
public class ApiAccessController {
private final ApiAccessService apiAccessService;
#Autowired
ApiAccessController(ApiAccessService apiAccessService){
this.apiAccessService = apiAccessService;
}
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id")Long id, Principal principal) {
return apiAccessService.generateApiKey(id, principal.getName());
}
}
My test looks as follow:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
Principal principal = new Principal() {
#Override
public String getName() {
return "TEST_PRINCIPAL";
}
};
#InjectMocks
ApiAccessController apiAccessController;
#Mock
ProjectRepository projectRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
Project project = new Project();
project.setId((long) 1);
project.setName("test");
project.setDescription("testdesc");
project.setCiid("ciid");
when(projectRepository.save(any(Project.class))).thenReturn(project);
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/" + project.getId() +"/apikey").principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Which is ment to create project and then run generateApiKey on this project, however I get NullpointerException looking like mocked controller cannot find created entity
could anyone please point me in the right direction as I am just starting with testing?
You should mock ApiAccessService instead of ProjectRepository.
Have a look at the code:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
private Principal principal = () -> "TEST_PRINCIPAL";
#InjectMocks
private ApiAccessController apiAccessController;
#Mock
private ApiAccessService apiAccessService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
long id = 1L;
when(apiAccessService.generateApiKey(id, principal.getName())).thenReturn(new ApiKeyResponse(111L));
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/{id}/apikey", id).principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
If you want to create integration test, that tests ApiAccessController -> ApiAccessService -> ProjectRepository integration you need to load your context (use for example #SpringBootTest).
Also you need to fix controller, use ResponseEntity.ok(...) :
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id") Long id, Principal principal) {
return ResponseEntity.ok(apiAccessService.generateApiKey(id, principal.getName()));
}
You can find really good examples of all test types in this repository MVC tests examples
The Mock you are creating is not referenced in the Controller. The Service you reference in the Controller is not part of your test setup. Therefore any access to the Service will cause a NullPointerException as the Service is not set.

How to inject MockMvc with multiple objects in Spring Boot REST Junit test case

I have a SpringBoot REST API connecting to Oracle DB.
My controller calls BusinessImpl layer and in-turn BusinessImpl calls multiple DAO layers (Controller -> BusinessImpl -> DAO1, DAO2, DAO3)
The below test case works perfectly
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration()
#TestPropertySource("classpath:dev-manifest.yml")
#ContextConfiguration(classes = Application.class)
#ConfigurationProperties(prefix = "env")
#SpringBootTest
public class MyTest
{
private static final String REQUEST_URI = "/v1/registration/accounts/links";
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup()
{
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void testSave()
{
String testInput = "some json input";
mockMvc.perform(post(REQUEST_URI).content(testInput).contentType(contentType))
.andExpect(status().isOk());
}
But I do not want to hit real database during junit test case. So I wrote a mock.
Working Code
#Mock
private SecurityAuditDAO securityAuditDAO;
#InjectMocks
private RegistrationBusinessImpl registrationBusinessImpl;
#Test
public void testSave()
{
when(securityAuditDAO.getState(Mockito.any())).thenReturn("somestring");
SomeRequest someRequest = new SomeRequest();
someRequest.setStatus("SUCCESS");
SomeResponse status = registrationBusinessImpl.createUser(SomeRequest, "127.0.0.1");
}
The above code worked perfectly. In businessImpl class securityAuditDAO.getState returned "somestring". But when I introduced mockMvc.perform it stopped working.
Not Working
#Test
public void testSave()
{
when(securityAuditDAO.getState(Mockito.any())).thenReturn("somestring");
String testInput = "some json input";
mockMvc.perform(post(REQUEST_URI).content(testInput).contentType(contentType))
.andExpect(status().isOk());
}
The above code was still hitting the database. So I realized that I should inject mockMvc with securityAuditDAO so I added the following line
this.mockMvc = MockMvcBuilders.standaloneSetup(securityAuditDAO).build();
Code
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Mock
private SecurityAuditDAO securityAuditDAO;
#InjectMocks
private RegistrationBusinessImpl registrationBusinessImpl;
#InjectMocks
RegistrationApiController registrationApiController;
#Before
public void setup()
{
MockitoAnnotations.initMocks(this);
//this.mockMvc = webAppContextSetup(webApplicationContext).build();
this.mockMvc = MockMvcBuilders.standaloneSetup(securityAuditDAO).build();
//this.mockMvc = MockMvcBuilders.standaloneSetup(registrationApiController).build();
//ReflectionTestUtils.setField(mockMvc, "securityAuditDAO", securityAuditDAO);
}
I tried injecting securityAuditDAO. But if I do that, my other autowired instances in BusinessImpl were null.
How to inject securityAuditDAO without affecting others or how to inject both webApplicationContext and securityAuditDAO.
Also tried ReflectionTestUtils.setField but it didn't work as expected.

Testing REST API with MOCKITO

I have a problem with testing the REST API using MOCKITO
I have an example of the rest controller code:
#RestController
#RequestMapping(path = "api/workers")
public class WorkOfferController {
#Autowired
private WorkerService workerService;
#PostMapping(value = "/lists", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity searchWorker(#RequestBody RecruitmentSearchRequest recruitmentSearchRequest, #RequestParam("page") int page, #RequestParam("size") int size,) throws NoSuchFieldException {
System.err.print('WorkerController');
return workerService.getWorkers(recruitmentSearchRequest, page, size);
}
}
And the right service for it:
#Service
#Transactional
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class WorkerService {
private final WorkerRepistory workerRepository;
private final UserRepository userRepository;
public ResponseEntity getWorkers(RecruitmentSearchRequest recruitmentSearchRequest,int pageNumber, int size) throws NoSuchFieldException {
System.err.print('WorkerService');
...
}
}
I want to test whether everything is okay under the url with the right data. I do not want to use this database because I prefer Mockito.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Appp2workApplication.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PharmacyWorkOfferRestDocsTests {
private MockMvc mockMvc;
#InjectMocks
private WorkOfferController workOfferController;
#Mock
private WorkerService workerService;
#Mock
UserRepository userRepository;
#Mock
WorkerRepistory workerRepository;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(workOfferController).build();
}
#Test
public void searchWorkOfferListWithParameters() throws Exception {
String payload = "{\n" +
"\t\"name\":\"tomek\",\n" +
"\t\"searchFromSalary\":5,\n" +
"}";
Mockito.doNothing().when(userRepository.save(Mockito.anyObject()));
Mockito.when(searchService.getRecruitmentOfferJobListWithParameters(Mockito.anyObject())).thenReturn(list);
this.mockMvc.perform(post("/api/workers/lists?page=0&size=20").contentType(MediaType.APPLICATION_JSON).content(payload))
.andExpect(status().isOk());
}
}
And I have a problem that with this approach the test comes to me in the controller and displays "WorkerController" but I do not want to enter the service from this controller, and it returns 200, but it really only came to the controller and that's it. This is probably because WorkerService is as Mock but I tried to give it as eg Autowired or InjectMock and it is still the same.
What do I do wrong that I enter into the controller but I do not want to use this controller for the appropriate service?

How can I test my SpringBoot RestController using a MockMvc when I rely on a Spring Validator?

In my rest-controller I am validating the input JSON with a custom Spring validator class.
When I now want to write unit test for the controller then I am getting the error that my Validator could not be found...
I am using constructor injecting for the two components in my rest-controller.
#Autowired
public JobController(JobValidator jobValidator, JobService jobService) {
this.jobValidator = jobValidator;
this.jobService = jobService;
}
And here my corresponding Test class.
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Appreciate any help, hints or suggestions!
So thanks to #pvpkiran! I had to add the JobValidator also as a Mock!
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#MockBean
private JobValidator jobValidator;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

Resources