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());
Related
i'm working in spring project where i have a service that call another api using Restemplate , this service return just a string as a token and all works fine for me , this is my service :
#Service
public class AppServiceImpl {
#Value("${rest.appUrl}")
private String appUrl;
#Value("${credType}")
private String credType;
#Autowired
private RestTemplate restTemplate;
public String getToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", credType);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST, request, TargetObject.class);
TargetObject targetObject = response.getBody();
return "Bearer " + targetObject.getToken();
}
}
my problem is when i want to unit test this service i'm getting NullPointerException and i dont know why , this is my unit test :
#RunWith(SpringRunner.class)
public class AppServiceImplTest {
private AppService appService = new AppServiceImpl();
#Test
public void getTokenTest() {
String token = appService.getToken();
assertTrue(token != null);
}
}
the NullPointerException in this line :
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST, request, TargetObject.class);
do you have any idea what's the problem with my test ? i spent hours without any result
Thanks in advance
your problem is that in your service you autowire restTemplate, but in your test you initialize your class by yourself, and not through spring, and there for it is not initialized and remains null.
if you are initializing the service by yourself in the test, make sure to add a mocked instance of restTemplate. I suggest moving the autowire in the service to be c'tor based, and then it will be easy to do so in the test:
#Service
public class AppServiceImpl {
private String appUrl;
private String credType;
private RestTemplate restTemplate;
public AppServiceImpl(RestTemplate restTemplate, #Value("${credType}") String credType, #Value("${rest.appUrl}") String appUrl) {
this.restTemplate = restTemplate;
this.credType = credType;
this.appUrl = appUrl;
}
...
...
...
}
and later in your test:
#Mock private RestTemplate restTamplate;
private AppService appService;
#Before
public void setup() {
appService = new AppServiceImpl(restTamplate);
}
Use Mockito It will resolve your issue.
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST,
request, TargetObject.class);
junit for above code would be:
#Mock
private RestTemplate restTemplate;
TargetObject targetObject = new TargetObject();
targetObject.setToken("123456");
Mockito.when( restTemplate.exchange(Mockito.anyString(),Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class),Mockito.any(TargetObject.class)).thenReturn(targetObject);
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>
I am doing the integration tests on a REST api where all the tests work but this one.
#Test
public void deleteAllUsers_should_return_noContent() throws Exception {
//Given
//When
ResultActions resultActions = mockMvc
.perform(delete("/pair?delAll"));
//Then
resultActions
.andDo(print())
.andExpect(status().isNoContent());
}
I am expecting a 204 http status but it gets a 400. Here is the code for the method:
#RequestMapping(value = "/pair", method = RequestMethod.DELETE, params = "delAll")
public ResponseEntity<Void> deleteAllUsers() {
userRepository.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
Any ideas to what am I doing wrong?
EDIT: Found the solution just adding "=true" to these lines
#RequestMapping(value = "/pair", method = RequestMethod.DELETE, params = "delAll=true")
ResultActions resultActions = mockMvc
.perform(delete("/pair?delAll=true"));
Working on a spring boot based Rest project I have a controller like this
which calls service and service layer call dao layer. Now I am writing unit test code for controllers. when I run this the error says
java.lang.AssertionError: expected:<201> but was:<415>
I don't know where I am doing wrong:
public class CustomerController {
private static final Logger LOGGER = LogManager.getLogger(CustomerController.class);
#Autowired
private CustomerServices customerServices;
#Autowired
private Messages MESSAGES;
#Autowired
private LMSAuthenticationService authServices;
#RequestMapping(value = "/CreateCustomer", method = RequestMethod.POST)
public Status createCustomer(#RequestBody #Valid Customer customer, BindingResult bindingResult) {
LOGGER.info("createCustomer call is initiated");
if (bindingResult.hasErrors()) {
throw new BusinessException(bindingResult);
}
Status status = new Status();
try {
int rows = customerServices.create(customer);
if (rows > 0) {
status.setCode(ErrorCodeConstant.ERROR_CODE_SUCCESS);
status.setMessage(MESSAGES.CUSTOMER_CREATED_SUCCESSFULLY);
} else {
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
} catch (Exception e) {
LOGGER.info("Cannot Create the Customer:", e);
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
return status;
}
}
The test for the CustomerController.
public class CustomerControllerTest extends ApplicationTest {
private static final Logger LOGGER = LogManager.getLogger(CustomerControllerTest.class);
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private CustomerController customerController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Status status = new Status(200,"customer created successfully","success");
String customer = "{\"customerFullName\":\"trial8900\",\"customerPhoneNumber\": \"trial8900\", \"customerEmailID\": \"trial8900#g.com\",\"alternateNumber\": \"trial8900\",\"city\": \"trial8900\",\"address\":\"hsr\"}";
#Test
public void testCreateCustomer() throws Exception {
String URL = "http://localhost:8080/lms/customer/CreateCustomer";
Mockito.when(customerController.createCustomer(Mockito.any(Customer.class),(BindingResult) Mockito.any(Object.class))).thenReturn(status);
// execute
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(TestUtils.convertObjectToJsonBytes(customer))).andReturn();
LOGGER.info(TestUtils.convertObjectToJsonBytes(customer));
// verify
MockHttpServletResponse response = result.getResponse();
LOGGER.info(response);
int status = result.getResponse().getStatus();
LOGGER.info(status);
assertEquals(HttpStatus.CREATED.value(), status);
}
}
HTTP status 415 is "Unsupported Media Type". Your endpoint should be marked with an #Consumes (and possibly also #Produces) annotation specifying what kinds of media types it expects from the client, and what kind of media type it returns to the client.
Since I see your test code exercising your production code with MediaType.APPLICATION_JSON_UTF8, you should probably mark your endpoint as consuming and producing APPLICATION_JSON_UTF8.
Then you also need to make sure that there is nothing terribly wrong going on in your error handling, because in the process of catching the exceptions generated by your production code and generating HTTP responses, your error handling code may be generating something different, e.g. generating an error status response with a payload containing an HTML-formatted error message, which would have a content-type of "text/html", which would not be understood by your test code which expects json.
Use the below base test class for your setUp and converting json to string and string to json
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public abstract class BaseTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
protected String mapToJson(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
}
Also verify that your post call has happened or not check the below sample
Mockito.doNothing().when(customerServices).create(Mockito.any(Customer.class));
customerServices.create(customer);
Mockito.verify(customerServices, Mockito.times(1)).create(customer);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI)
.accept(MediaType.APPLICATION_JSON).content(inputInJson)
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());
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.