I currently have a spring boot application (version - 2.1.8.RELEASE) which exposed rest webservice that provide some basic email functionality. I have added swagger component to it and can test the webservice through swagger UI interface. For example when I test the webservice through swagger UI and I don't input mandatory field such as mailfrom I got a error message that states emailfrom is missing with a status code which is what i am expecting.
But when the same scenario is carried out with a spring mvc project(client for web service - spring version 3.2.5.RELEASE) no description of the error is displayed as i was expecting to have the same behavior when testing with swagger Ui above. Please see screenshot result when carrying out integration test below the result is 500 null:
Pleas find below spring boot application rest controller below:
#RestController
#RequestMapping("/email")
public class EmailController {
private static final Logger LOG = LoggerFactory.getLogger(EmailController.class);
#Autowired
SendMailService sendMailService;
#ApiOperation(value = "This service send mail based on the information provided from EmailRequestDto", response = String.class)
#PostMapping(value = "/sendMail")
public #ResponseBody ResponseEntity<String> sendMail(#RequestBody EmailRequestDto emailRequestDto) {
LOG.debug("calling method sendMail");
sendMailService.sendEmail(emailRequestDto);
return new ResponseEntity<>("Mail has been sent successfully", HttpStatus.OK);
}
Please find below part of service class for sending e mail:
#Component
public class SendMailServiceImpl implements SendMailService {
private static final Logger LOG = LoggerFactory.getLogger(SendMailServiceImpl.class);
#Autowired
private JavaMailSender javaMailSender;
/**
* {#inheritDoc}
*/
#Override
public void sendEmail(EmailRequestDto emailRequestDto) {
LOG.debug("calling method sendMail");
if (!ObjectUtils.isEmpty(emailRequestDto)) {
MimeMessage msg = javaMailSender.createMimeMessage();
// true = multipart message
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(msg, true);
helper.setFrom(emailRequestDto.getMailFrom());
helper.setTo(emailRequestDto.getMailTo().stream().toArray(String[]::new));
helper.setSubject(emailRequestDto.getSubject());
helper.setText(emailRequestDto.getEmailContent(), true);
// helper.addAttachment("my_photo.png", new ClassPathResource("android.png"));
javaMailSender.send(msg);
} catch (MessagingException e) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Error occurred while send mail", e);
}
}
}
Please find below my integration test for calling the web service from the client project:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:dummy-service-test-context.xml" })
public class SendMailServiceTest {
#Test
public void testService() {
final String uri = "http://localhost:8080/email-service/email/sendMail";
EmailRequestDto emailRequestDto = new EmailRequestDto();
// emailRequestDto.setMailTo((Arrays.asList("dummy#gmail.com")));
// emailRequestDto.setEmailContent("Dear Sir");
// emailRequestDto.setMailFrom("dummy_38#hotmail.com");
// emailRequestDto.setSubject("Sending Email subject");
emailRequestDto.setMailTo(null);
emailRequestDto.setEmailContent(null);
emailRequestDto.setMailFrom(null);
emailRequestDto.setSubject(null);
HttpEntity<EmailRequestDto> request = new HttpEntity<>(emailRequestDto);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> es = restTemplate.exchange(uri, HttpMethod.POST, request,String.class);
// ResponseEntity<?> response = restTemplate.postForEntity(uri, request, ResponseEntity.class, new HashMap());
Assert.assertTrue(es.getStatusCode().equals(HttpStatus.OK));
}
}
Please find my emailDto below:
#Getter
#Setter
#ApiModel
public class EmailRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> mailTo;
#ApiModelProperty(value = "Email address who is sending the mail", required = true)
#NonNull
private String mailFrom;
#ApiModelProperty(value = "Subject of the mail", required = true)
#NonNull
private String subject;
#ApiModelProperty(value = "The content of the mail", required = true)
#NonNull
private String emailContent;
private List<String> bcc;
#ApiModelProperty(value = "This attribute identify if the mail content should be sent as html or plain text", required = true)
#NonNull
boolean fileHtml;
Can anyone point to me what i am doing wrong and why i am getting 500 null please?
When I insert the dateFrom null purposely to force the application to fail in the spring boot tomcat log the exception is correct :
mailFrom is mandatory:
nested exception is com.fasterxml.jackson.databind.JsonMappingException: mailFrom is marked non-null but is null
But in my client integration test it is :
org.springframework.web.client.HttpClientErrorException: 400 null
Related
I am unable to get the mocked response from Feign Client. I provide below the code.
In the service class, it has been written like this.
public String getInfo(HttpServletRequest request, String id, String type) {
.... other code .....
try {
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
ObjectNode node = new ObjectMapper().readValue(statusAsJsonString, ObjectNode.class);
if (node.has(CommonConstants.STATUS)) {
statusValue = node.get(CommonConstants.STATUS).asText();
}
} catch (FeignException fe) {
byte[] contents = fe.content();
String jsonContents = null;
if(contents != null) {
jsonContents = new String(contents);
}
statusValue = getErrorParsedStatusValue(jsonContents);
} catch (Exception ex) {
ex.printStackTrace();
}
log.debug("status: " + statusValue);
return statusValue;
}
In the unit test, I am trying to write in the following manner.
String responseBody = "[]";
when(myFeignClient.getStatus("cookievalue", "id", "SOme-Value")).thenReturn(responseBody);
I have also used, WireMock to achieve it.
wireMockServer.stubFor(WireMock.get("/rest/v1/somna/{id}/phase").withRequestBody(WireMock.equalToJson("{ \"name\": \"Phone\", \"initialStock\": 3}"))
.willReturn(WireMock.okJson(responseBody)));
The following piece of code is never covered and executed.
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
Also the invocation of Feign client is inside a service method, first want to get the mocked result of that Feign client.
PLease help me.
I provide below my Feign CLient
#FeignClient(name = CommonConstants.FEIGN_CLIENT_NAME, url = "${feign.service.url}", primary = false)
public interface MyFeignClient {
#GetMapping(value = "/rest/v1/project/{id}/phaseName")
String getStatus(#RequestHeader("Cookie") String cookie,
#PathVariable("id") Stringid, #RequestParam("type") String type);
}
In my test class, I have added the followings.
#Autowired
private MyServiceImpl readyService = new MyServiceImpl();
#Mock
private MyFeignClient myFeignClient;
#ClassRule
public static WireMockServer wireMockServer = new WireMockServer(new WireMockConfiguration().port(8088));
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
httpServletRequest = Mockito.mock(HttpServletRequest.class);
ReflectionTestUtils.setField(someService, "cookieName", "cookieName");
wireMockServer.start();
}
Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/
Please have a look at my codes below. The Java codes seemed to work just fine, but localhost:8080 gives me the error code 404 when I try to access it. I want to make localhost 8080 work. Please let me know if you need further information.
Application
#SpringBootApplication(exclude = { ErrorMvcAutoConfiguration.class })
// exclude part is to elimnate whitelabel error
#EnableScheduling
public class Covid19TrackerApplication {
public static void main(String[] args) {
SpringApplication.run(Covid19TrackerApplication.class, args);
}
}
Controller
#Controller
public class HomeController {
CovidDataService covidDataService;
#RequestMapping("/")
public #ResponseBody String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
}
Main Code
#Service
public class CovidDataService {
private static String Covid_Data_URL = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv";
private List<LocationStats> allStats = new ArrayList<>();
public List<LocationStats> getAllStats() {
return allStats;
}
#PostConstruct//?
#Scheduled(cron = "* * 1 * * *") //????
// * sec * min *hour and so on
public void fetchCovidData() throws IOException, InterruptedException {
List<LocationStats> newStats = new ArrayList<>(); // why we are adding this? To prevent user get an error while we are working on new data.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(Covid_Data_URL))
.build(); // uri = uniform resource identifier
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
StringReader csvBodyReader = new StringReader(httpResponse.body()); //StringReader needs to be imported
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(csvBodyReader); // parse(in) had error, we needed a "reader" instance.
for (CSVRecord record : records) {
LocationStats locationStat = new LocationStats(); //create an instance
locationStat.setState(record.get("Province/State"));
locationStat.setCountry(record.get("Country/Region"));
locationStat.setLatestTotalCase(Integer.parseInt(record.get(record.size()-1)));
System.out.println(locationStat);
newStats.add(locationStat);
}
this.allStats = newStats;
}
}
The problem may come from this piece of code
#RequestMapping("/")
public #ResponseBody String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
it returns "home" which should be existing view, normally, the view will be a jsp file which is placed somewhere in WEB-INF, please see this tutorial: https://www.baeldung.com/spring-mvc-view-resolver-tutorial
In the case of wrong mapping, it may returns 404 error
when you run the server, you should be able to see which port it's taken in the console.
Also, is server.port=8080 in the src/main/resources/application.properties file?
In the controller, the RequestMapping annotation is missing the method type and header
#RequestMapping(
path="/",
method= RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
make sure to add consumes for POST or PUT methods
A bit unrelated to the question but the line in the controller is missing #Autowired annotation
CovidDataService covidDataService;
Preferrably, add the #Autowired in the constructor
#Autowired
public HomeController(CovidDataService covidDataService) {
this.covidDataService = covidDataService;
}
I have one simple test to write for a POST method in a RestController. This post mapping works fine when i run the application. But, in it's test i always get HttpMessageNotReadableException with 400-Bad Request.
InternalCustomerController.class
#RestController
#RequestMapping(
value = "/",
consumes = {MediaType.APPLICATION_XML_VALUE},
produces = {MediaType.APPLICATION_XML_VALUE})
#RequiredArgsConstructor
public class InternalCustomerController {
private final CustomerService customerService;
#PostMapping(value = "/cpdupdate")
public Notification updateCustomerProduct(#NotNull #RequestBody Customer customer) {
return customerService.handleUpdateCustomerProduct(customer);
}
}
InternalCustomerControllerTest.class
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = InternalCustomerController.class)
#Import(TestObjectMapperConfig.class)
public class InternalCustomerControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CustomerService customerService;
#Test
public void testUpdateCustomerProduct() throws Exception {
Customer validCustomer = createValidCustomer("76d211bb680843c5b63afa1c13a0a5e5", "60");
when(this.customerService.handleUpdateCustomerProduct(any()))
.thenReturn(buildDummyNotification(messageId, "SUCCESS"));
mockMvc.perform(post("/cpdupdate")
.contentType(MediaType.APPLICATION_XML)
.content(objectToString(validCustomer)))
.andExpect(status().isOk()); // Fails Here as it gets 400 in return
}
// Customer Class Generated from Provided XSD on maven build
private static Customer createValidCustomer(String internalProductId, int customerId) {
Customer customer = new Customer();
customer.setCustomerId(customerId);
Customer.InternalProducts products = new Customer.InternalProducts();
products.getProductIds().add(internalProductId);
customer.setInternalProducts(products);
return customer;
}
private Notification buildDummyNotification(String messageId, String message) {
return Notification.builder()
.messageId(messageId)
.message(message)
.build();
}
private static String objectToString(Object element) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
JAXBContext context = JAXBContext.newInstance(element.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(element, outputStream);
return outputStream.toString("UTF-8");
} catch (JAXBException | IOException ex) {
log.error(ex.getMessage(), ex);
return "";
}
}
}
Error message contains JSON parse error: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token. So i tried adding a configuration for ObjectMapper and Import it into the test.
#Configuration
public class TestObjectMapperConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return objectMapper;
}
}
But i still get the error.
What i am missing here?
Full Error Message
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: java.io.PushbackInputStream#53311681; line: 4, column: 49] (through reference chain: com.clps.Customer["internalProducts"]->com.clps.Customer$InternalProducts["productId"])]
MockHttpServletRequest:
HTTP Method = POST
Request URI = /cpdupdate
Parameters = {}
Headers = {Content-Type=[application/xml]}
Handler:
Type = com.pt.controllers.InternalCustomerController
Method = public com.pt.models.Notification com.pt.controllers.InternalCustomerController.updateCustomerProduct(com.clps.Customer)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :400
I have a Rest controller similar to this one:
#RestController
public class UserRestController {
#Autowired
private UserService userService;
#RequestMapping(value = "/user/activate", method = RequestMethod.POST)
public ResponseEntity<UserDTO> activate(
#RequestParam(required = true) final String email,
#RequestParam(required = true) final String key) {
UserDTO userDTO = userService.activateAccount(email, key);
return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
}
}
When I invoke it using Postman and I don't send the 'key' parameter, I receive this JSON message:
{
"timestamp": 1446211575193,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'key' is not present",
"path": "/user/activate"
}
On the other hand, I am testing this method with JUnit and the MockMVC Spring utility.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ApplicationConfig.class)
#WebAppConfiguration
public class UserRestControllerTest {
private static MockMvc mockMvc;
#Mock
private UserService userService;
#InjectMocks
private UserRestController userRestController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(userRestController)
.setMessageConverters(
new MappingJackson2HttpMessageConverter(),
new Jaxb2RootElementHttpMessageConverter()).build();
}
#Test
public void testActivateRequiredParams() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post("/user/activate")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(
MockMvcResultMatchers.content().contentType(
UtilsUnitTest.APPLICATION_JSON_UTF8))
.andExpect(
jsonPath(
"message",
is("Required String parameter 'email' is not present")));
}
}
But when I execute this test I notice that the response is not a JSON message. In fact, I get an exception:
java.lang.AssertionError: Content type not set
Particularly, the completed result is
MockHttpServletRequest:
HTTP Method = POST
Request URI = /user/activate
Parameters = {}
Headers = {Content-Type=[application/x-www-form-urlencoded]}
Handler:
Type = com.company.controller.UserRestController
Method = public org.springframework.http.ResponseEntity<com.company.dto.UserDTO> com.company.controller.UserRestController.activate(java.lang.String,java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.bind.MissingServletRequestParameterException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = Required String parameter 'email' is not present
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I deduce that when an exception is thrown this is not converted to JSON format (I get a correct JSON result when I send the email and key parameters)
The question is obvious: what should I change in the Unit Test configuration to get a JSON error message in case of an exception be thrown?
The problem is that the test stops after following assertion fails:
.andExpect(MockMvcResultMatchers.content().contentType(UtilsUnitTest.APPLICATION_JSON_UTF8))
Beside this, jsonPath("message"... won't hit anything. To validate the returned error message use MockMvcResultMatchers.status().reason(<ResultMatcher>).
The following test should do the job:
#Test
public void testActivateRequiredParams() throws Exception {
final ResultActions result = mockMvc
.perform(MockMvcRequestBuilders.post("/user/activate")
.contentType(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print());
result.andExpect(MockMvcResultMatchers.status().isBadRequest());
result.andExpect(MockMvcResultMatchers.status().reason(is("Required String parameter 'email' is not present")));
}
But think about if this (testing for error messages) is a good idea in general, maybe see this discussion.