Mockito unit test for rest template - java

I am trying to write a unit test for rest template that is making http calls. I have created the rest template with the rest template builder, shown below. the rest template is set to configure read and connection timeouts. I also have a retry template that is to execute retries when the application has a timeout. I am at the point where I have specified the http methods: postForEntity, exchange, and getForEntity that need to retried in the retry template and need help with writing unit tests. I started with the getForEntity method but am receiving a different output from what is expected. any assistance with this would be helpful.
Rest Template
#Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
Retrying getForEntity
public ResponseEntity getForEntity(URI uri, Class c) {
return retryTemplate.execute(retryContext -> {
return restTemplate.getForEntity(uri, c);
});
}
Unit Test
public class RetryRestTemplateTest {
#Mock
private RestTemplate restTemplate;
#Mock
private RetryTemplate retryTemplate;
private RetryRestTemplate retryRestTemplate;
String testUrl = "http://localhost:8080";
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
retryRestTemplate = new RetryRestTemplate(
restTemplate,
retryTemplate
);
}
#Test
public void getForEntity() throws URISyntaxException{
URI testUri= new URI(testUrl);
ArgumentCaptor<URI> argument = ArgumentCaptor.forClass(URI.class);
doReturn(new ResponseEntity<>("ResponseString", HttpStatus.OK))
.when(restTemplate).getForEntity(any(URI.class), eq(String.class));
assertThat(restTemplate.getForEntity(testUri, String.class), is(HttpStatus.OK));
verify(restTemplate).getForEntity(argument.capture(), eq(String.class));
assertThat(argument.getValue().toString(), is(testUri));
}}
My expected should be is <200 OK> and my actual is <<200 OK OK,ResponseString,[]>>
Any help on this would be helpful as I am not that experienced with Mockito and Junit.

Your expectations are not aligned with the code you wrote getForEntity does not return a HttpStatus instance, instead it returns a ResponseEntity<String>. Comparing a ResponseEntity<String> with a HttpStatus will never yield equal.
A fixed version of your test:
#Test
public void getForEntity() throws URISyntaxException {
URI testUri = new URI(testUrl);
ArgumentCaptor<URI> argument = ArgumentCaptor.forClass(URI.class);
doReturn(new ResponseEntity<>("ResponseString", HttpStatus.OK))
.when(restTemplate).getForEntity(any(URI.class), eq(String.class));
assertThat(restTemplate.getForEntity(testUri, String.class).getStatusCode(),
CoreMatchers.is(HttpStatus.OK));
verify(restTemplate).getForEntity(argument.capture(), eq(String.class));
assertThat(argument.getValue(), CoreMatchers.is(testUri));
}
Some side note: The test does not really test getForEntity, it tests that a java proxy you created with mockito does return the mocked result. Imho you are actually testing if the mock framework works...
doReturn(new ResponseEntity<>("ResponseString", HttpStatus.OK)).when(restTemplate).getForEntity(any(URI.class), eq(String.class));
As discussed in the comments, an integration test of the RestTemplate could be:
package com.example.demo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.IOException;
import java.net.URI;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class RetryRestTemplateTest {
private final MockWebServer server = new MockWebServer();
#BeforeEach
public void setup() throws IOException {
server.start();
}
#AfterEach
public void teardown() throws IOException {
server.close();
}
#Test
public void getForEntity() {
URI testUri = server.url("/").uri();
server.enqueue(new MockResponse().setResponseCode(200).setBody("{}"));
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> forEntity = restTemplate.getForEntity(testUri, String.class);
assertThat(forEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(forEntity.getBody(), is("{}"));
}
}
The following test dependencies are needed:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>

Related

Why Getting Empty Response when writing UnitTest

I am learning to write Unit Test for SpringBoot Restcontroller , wrote this and test passes
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {FhirApp.class, TestSecurityConfiguration.class})
#AutoConfigureMockMvc
public class ObservationControllerTest {
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
private MockMvc mockMvc;
#MockBean
private ObservationService observationService;
#Test
public void createObservationResource() throws Exception {
given(observationService.createObservation(ResourceStringProvider.observationsource()))
.willReturn(responseDocument);
String jsonString = objectMapper.writeValueAsString(
ResourceStringProvider.observationsource());
mockMvc.perform(post("/Observation")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonString))
.andExpect(status()
.isOk());
}
But as this and this , i am also getting empty response for response.getContentAsString() :
Mockito.when(observationService.createObservation(Mockito.any())).thenReturn(responseDocument);
String jsonString = objectMapper.writeValueAsString(ResourceStringProvider.observationsource());
MockHttpServletResponse response = mockMvc.perform(post("/Observation")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonString))
.andReturn()
.getResponse();
assertThat(response.getContentAsString())
.isEqualTo(new ObjectMapper()
.writeValueAsString(responseDocument));
I already tried the solutions provided by them :
1: Using Mockito.any(String.class)
2: webEnvironment = SpringBootTest.WebEnvironment.MOCK
3: using thenCallRealMethod instead of thenReturn(responseDocument)
But unfortunately it didn't work,already tried different possibilities , I also tried using MockitoJunitRunner :
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(classes = {FhirApp.class, TestSecurityConfiguration.class})
#AutoConfigureMockMvc
public class ObservationControllerTest {
private MockMvc mockMvc;
#Mock
private ObservationService observationService;
#Mock
private RequestFilter requestFilter;
#InjectMocks
private ObservationController observationController;
#Before
public void setup() {
Resource resource = Utility.convertFromStringToFhirResource(Observation.class,ResourceStringProvider.observationResponse());
responseDocument=Utility.convertFromFhirResourceToMongoInsertibleDoc(resource);
//these line enabled for MockitoJUnitRunner only
this.mockMvc = MockMvcBuilders.standaloneSetup(observationController)
.setControllerAdvice(new FhirRuntimeException("Error Happened"))
.addFilters(requestFilter)
.build();
}
#Test
public void createObservationResource()throws Exception{
Mockito.when(observationService.createObservation(ResourceStringProvider.observationsource())).thenReturn(responseDocument);
String jsonString = objectMapper.writeValueAsString(ResourceStringProvider.observationsource());
MockHttpServletResponse response = mockMvc.perform(
post("/Observation")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonString))
.andReturn()
.getResponse();
assertThat(response.getContentAsString()).isEqualTo(new ObjectMapper().writeValueAsString(responseDocument));
}
i think since not many have went through such issues, issue isn't much talked about . What could be the reason of empty response when response status is ok?
Controller code :
#RestController
#RequestMapping("/api")
public class ObservationController {
#Autowired
private ObservationService observationService;
#GetMapping("/Observation/{id}")
public ResponseEntity<Document> getObservationByID(
#RequestParam("_pretty") Optional<String> pretty,
#PathVariable("id") String id) {
Document resultDoc = observationService.getObservationById(id);
return new ResponseEntity<>(resultDoc, HttpStatus.OK);
}
#PostMapping(path = "/Observation", consumes = {"application/json", "application/fhir+json"},
produces = {"application/json", "application/fhir+json"})
public ResponseEntity<Document> createObservationResource(#RequestBody String fhirResource) {
Document fhirDoc = observationService.createObservation(fhirResource);
return new ResponseEntity<>(fhirDoc,
Utility.createHeaders(fhirDoc),
HttpStatus.CREATED);
}
//other methods
}
I realized the call to post in test should be /api/Observation , but it didn't make any difference . Thanks in Advance.
Posting the complete answer alongwith imports here so that it will help someone (actually we don't require any annotation above and it is one way to run it , during my findings i came accross this post which helps in solving design issue which may occur if you go ahead with #InjectMocks approach)
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import com.comitemd.emr.datalayer.fhir.service.ObservationService;
import com.comitemd.emr.datalayer.fhir.utility.ResourceStringProvider;
import com.comitemd.emr.datalayer.fhir.utility.Utility;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bson.Document;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Resource;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
//#RunWith(MockitoJUnitRunner.class)
public class ObservationControllerStandaloneTest {
private MockMvc mockMvc;
#Mock
private ObservationService observationService;
#InjectMocks
private ObservationController observationController;
private Document responseDocument;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);// enable this or MockitoJUnitRunner.class
Resource resource = Utility
.convertFromStringToFhirResource(Observation.class, ResourceStringProvider.observationResponse());
responseDocument=Utility.convertFromFhirResourceToMongoInsertibleDoc(resource);
this.mockMvc = MockMvcBuilders.standaloneSetup(observationController).build();
}
#Test
public void createObservationResource()throws Exception{
Mockito.when(observationService.createObservation(ResourceStringProvider.observationsource()))
.thenReturn(responseDocument);
MockHttpServletResponse response = mockMvc.perform(
post("/api/Observation")
.contentType(MediaType.APPLICATION_JSON)
.content(ResourceStringProvider.observationsource()))
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse();
verify(observationService, times(1)).createObservation(Mockito.any());
assertThat(response.getContentAsString()).isEqualTo(new ObjectMapper().writeValueAsString(responseDocument));
}
}

MockMvc: Cannot resolve print method in andDo(print())

I'm new to JUnit testing. I'm now trying to test a Spring endpoint using MockMvc, but the andDo(print()) method can not be found.
Are there any things I have to import in order to use this or what?
#Autowired
private MockMvc mockMvc;
#Test
public void compareDeleteTest() throws Exception{
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
RequestBuilder requestBuilder = MockMvcRequestBuilders.delete("api/compare/3")
.headers(httpHeaders);
MvcResult result = mockMvc.perform(requestBuilder)
.andDo(print());
}
Here's what shown in my IDE:
Use Java's static imports to be able to call it without class name or any object:
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
I think the print() you are looking for is in MockMvcResultHandlers
Here is how you can do it -
ResultActions resultActions = mockMvc.perform(requestBuilder)
.andDo(MockMvcResultHandlers.print());

Spring WebTestClient JSON LocalDate decode

When using WebTestClient in Spring Boot 2.0.1 I get different formatted dates depending on how I bind the test client see code below.
So how can I get the WebTestClient.bindToController to return LocalDate formatted as 2018-04-13? When I call WebTestClient.bindToServer() I get expected format.
#RestController
public class TodayController {
#GetMapping("/today")
public Map<String, Object> fetchToday() {
return ImmutableMap.of("today", LocalDate.now());
}
}
Tests:
#ExtendWith({SpringExtension.class})
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TodayControllerTest {
#LocalServerPort
private int randomPort;
#Autowired
private TodayController controller;
#Test
void fetchTodayWebTestClientBoundToController() {
WebTestClient webTestClient = WebTestClient.bindToController(controller)
.configureClient()
.build();
webTestClient.get().uri("/today")
.exchange()
.expectBody()
.json("{\"today\":[2018,4,13]}");
}
#Test
void fetchTodayWebTestClientBoundToServer() {
WebTestClient webTestClient = WebTestClient.bindToServer()
.baseUrl("http://localhost:" + randomPort)
.build();
webTestClient.get().uri("/today")
.exchange()
.expectBody()
.json("{\"today\":\"2018-04-13\"}");
}
As it turns out I need to set the Jackson decoder/encoder when using WebTestClient.bindToController. e.g.
#Test
public void fetchTodayWebTestClientBoundToController() {
WebTestClient webTestClient = WebTestClient.bindToController(controller)
.httpMessageCodecs((configurer) -> {
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
defaults.jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, new MimeType[0]));
defaults.jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, new MimeType[0]));
})
.configureClient()
.build();
webTestClient.get().uri("/today")
.exchange()
.expectBody()
.json("{\"today\":\"2018-04-30\"}");
}
More detailed answer from Spring boot project
Add this file with configuration
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.reactive.config.WebFluxConfigurer;
#Configuration
#Import(JacksonAutoConfiguration.class)
#AutoConfigureAfter(JacksonAutoConfiguration.class)
public class JacksonTestConfiguration {
#Bean
WebFluxConfigurer webFluxConfigurer(ObjectMapper objectMapper) {
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
}
};
}
}

Can't upload multipart file with mockMvc [duplicate]

I have the following request handler for saving autos. I have verified that this works when I use e.g. cURL. Now I want to unit test the method with Spring MVC Test. I have tried to use the fileUploader, but I am not managing to get it working. Nor do I manage to add the JSON part.
How would I unit test this method with Spring MVC Test? I am not able to find any examples on this.
#RequestMapping(value = "autos", method = RequestMethod.POST)
public ResponseEntity saveAuto(
#RequestPart(value = "data") autoResource,
#RequestParam(value = "files[]", required = false) List<MultipartFile> files) {
// ...
}
I want to uplod a JSON representation for my auto + one or more files.
I will add 100 in bounty to the correct answer!
Since MockMvcRequestBuilders#fileUpload is deprecated, you'll want to use MockMvcRequestBuilders#multipart(String, Object...) which returns a MockMultipartHttpServletRequestBuilder. Then chain a bunch of file(MockMultipartFile) calls.
Here's a working example. Given a #Controller
#Controller
public class NewController {
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public String saveAuto(
#RequestPart(value = "json") JsonPojo pojo,
#RequestParam(value = "some-random") String random,
#RequestParam(value = "data", required = false) List<MultipartFile> files) {
System.out.println(random);
System.out.println(pojo.getJson());
for (MultipartFile file : files) {
System.out.println(file.getOriginalFilename());
}
return "success";
}
static class JsonPojo {
private String json;
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
}
}
and a unit test
#WebAppConfiguration
#ContextConfiguration(classes = WebConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class Example {
#Autowired
private WebApplicationContext webApplicationContext;
#Test
public void test() throws Exception {
MockMultipartFile firstFile = new MockMultipartFile("data", "filename.txt", "text/plain", "some xml".getBytes());
MockMultipartFile secondFile = new MockMultipartFile("data", "other-file-name.data", "text/plain", "some other type".getBytes());
MockMultipartFile jsonFile = new MockMultipartFile("json", "", "application/json", "{\"json\": \"someValue\"}".getBytes());
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(MockMvcRequestBuilders.multipart("/upload")
.file(firstFile)
.file(secondFile)
.file(jsonFile)
.param("some-random", "4"))
.andExpect(status().is(200))
.andExpect(content().string("success"));
}
}
And the #Configuration class
#Configuration
#ComponentScan({ "test.controllers" })
#EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}
}
The test should pass and give you output of
4 // from param
someValue // from json file
filename.txt // from first file
other-file-name.data // from second file
The thing to note is that you are sending the JSON just like any other multipart file, except with a different content type.
The method MockMvcRequestBuilders.fileUpload is deprecated use MockMvcRequestBuilders.multipart instead.
This is an example:
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
/**
* Unit test New Controller.
*
*/
#RunWith(SpringRunner.class)
#WebMvcTest(NewController.class)
public class NewControllerTest {
private MockMvc mockMvc;
#Autowired
WebApplicationContext wContext;
#MockBean
private NewController newController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wContext)
.alwaysDo(MockMvcResultHandlers.print())
.build();
}
#Test
public void test() throws Exception {
// Mock Request
MockMultipartFile jsonFile = new MockMultipartFile("test.json", "", "application/json", "{\"key1\": \"value1\"}".getBytes());
// Mock Response
NewControllerResponseDto response = new NewControllerDto();
Mockito.when(newController.postV1(Mockito.any(Integer.class), Mockito.any(MultipartFile.class))).thenReturn(response);
mockMvc.perform(MockMvcRequestBuilders.multipart("/fileUpload")
.file("file", jsonFile.getBytes())
.characterEncoding("UTF-8"))
.andExpect(status().isOk());
}
}
Have a look at this example taken from the spring MVC showcase, this is the link to the source code:
#RunWith(SpringJUnit4ClassRunner.class)
public class FileUploadControllerTests extends AbstractContextControllerTests {
#Test
public void readString() throws Exception {
MockMultipartFile file = new MockMultipartFile("file", "orig", null, "bar".getBytes());
webAppContextSetup(this.wac).build()
.perform(fileUpload("/fileupload").file(file))
.andExpect(model().attribute("message", "File 'orig' uploaded successfully"));
}
}
Here's what worked for me, here I'm attaching a file to my EmailController under test. Also take a look at the postman screenshot on how I'm posting the data.
#WebAppConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = EmailControllerBootApplication.class
)
public class SendEmailTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Test
public void testSend() throws Exception{
String jsonStr = "{\"to\": [\"email.address#domain.com\"],\"subject\": "
+ "\"CDM - Spring Boot email service with attachment\","
+ "\"body\": \"Email body will contain test results, with screenshot\"}";
Resource fileResource = new ClassPathResource(
"screen-shots/HomePage-attachment.png");
assertNotNull(fileResource);
MockMultipartFile firstFile = new MockMultipartFile(
"attachments",fileResource.getFilename(),
MediaType.MULTIPART_FORM_DATA_VALUE,
fileResource.getInputStream());
assertNotNull(firstFile);
MockMvc mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext).build();
mockMvc.perform(MockMvcRequestBuilders
.multipart("/api/v1/email/send")
.file(firstFile)
.param("data", jsonStr))
.andExpect(status().is(200));
}
}
If you are using Spring4/SpringBoot 1.x, then it's worth mentioning that you can add "text" (json) parts as well . This can be done via MockMvcRequestBuilders.fileUpload().file(MockMultipartFile file) (which is needed as method .multipart() is not available in this version):
#Test
public void test() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.fileUpload("/files")
// file-part
.file(makeMultipartFile( "file-part" "some/path/to/file.bin", "application/octet-stream"))
// text part
.file(makeMultipartTextPart("json-part", "{ \"foo\" : \"bar\" }", "application/json"))
.andExpect(status().isOk())));
}
private MockMultipartFile(String requestPartName, String filename,
String contentType, String pathOnClassPath) {
return new MockMultipartFile(requestPartName, filename,
contentType, readResourceFile(pathOnClasspath);
}
// make text-part using MockMultipartFile
private MockMultipartFile makeMultipartTextPart(String requestPartName,
String value, String contentType) throws Exception {
return new MockMultipartFile(requestPartName, "", contentType,
value.getBytes(Charset.forName("UTF-8")));
}
private byte[] readResourceFile(String pathOnClassPath) throws Exception {
return Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader()
.getResource(pathOnClassPath).toUri()));
}
}

unit test Spring MissingServletRequestParameterException JSON response

I have POST method in a Spring boot rest controller as follows
#RequestMapping(value="/post/action/bookmark", method=RequestMethod.POST)
public #ResponseBody Map<String, String> bookmarkPost(
#RequestParam(value="actionType",required=true) String actionType,
#RequestParam(value="postId",required=true) String postId,
#CurrentUser User user) throws Exception{
return service.bookmarkPost(postId, actionType, user);
}
now if I test with missing parameter in Postman I get an 400 http response and a JSON body:
{
"timestamp": "2015-07-20",
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'actionType' is not present",
"path": "/post/action/bookmark"
}
until now it's OK, but when I try to unit test I don't get the JSON response back
#Test
public void bookmarkMissingActionTypeParam() throws Exception{
// #formatter:off
mockMvc.perform(
post("/post/action/bookmark")
.accept(MediaType.APPLICATION_JSON)
.param("postId", "55ab8831036437e96e8250b6")
)
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.exception", containsString("MissingServletRequestParameterException")));
// #formatter:on
}
the test fails and produces
java.lang.IllegalArgumentException: json can not be null or empty
I did a .andDo(print()) and found that there is no body in the response
MockHttpServletResponse:
Status = 400
Error message = Required String parameter 'actionType' is not present
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store], Pragma=[no-cache], Expires=[1], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
why am I not getting the JSON response while unit testing my controller, but do receive it in manual testing using Postman or cUrl?
EDIT: I've added #WebIntegrationTest but got the same error:
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = RestApplication.class)
#WebIntegrationTest
public class PostControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
#Test
public void bookmarkMissingActionTypeParam() throws Exception{
// #formatter:off
mockMvc.perform(
post("/post/action/bookmark")
.accept(MediaType.APPLICATION_JSON)
.param("postId", "55ab8831036437e96e8250b6")
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.exception", containsString("MissingServletRequestParameterException")));
// #formatter:on
}
}
This is because Spring Boot has auto-configured an exception handler org.springframework.boot.autoconfigure.web.BasicErrorController which is probably not present in your unit tests. A way to get it will be to use the Spring Boot testing support related annotations:
#SpringApplicationConfiguration
#WebIntegrationTest
More details are here
Update:
You are absolutely right, the behavior is very different in UI vs in test, the error pages which respond to status codes are not correctly hooked up in a non-servlet test environment. Improving this behavior can be a good bug to open for Spring MVC and/or Spring Boot.
For now, I have a workaround which simulates the behavior of BasicErrorController the following way:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {RestApplication.class, TestConfiguration.class})
#WebIntegrationTest
public class PostControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
#Test
public void bookmarkMissingActionTypeParam() throws Exception{
// #formatter:off
mockMvc.perform(
post("/post/action/bookmark")
.accept(MediaType.APPLICATION_JSON)
.param("postId", "55ab8831036437e96e8250b6")
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.exception", containsString("MissingServletRequestParameterException")));
// #formatter:on
}
}
#Configuration
public static class TestConfiguration {
#Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
return new ErrorController(errorAttributes);
}
}
#ControllerAdvice
class ErrorController extends BasicErrorController {
public ErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#Override
#ExceptionHandler(Exception.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
return super.error(request);
}
}
What I am doing here is adding a ControllerAdvice which handles the Exception flow and delegates back to the BasicErrorController. This would atleast make the behavior consistent for you.
Originally, it should fix the error by #ResponseBody tag when defining your REST controller method. it will fix json error in the test class.
But, as you are using spring boot, you will define the controller class with #RestController and it should automatically take care of the error without defining #Controller and #ResponseType tags.

Categories