I have a demo filter to log request/response body and verify the request signature, if signature is wrong the filter would be redirect to my error controller. And I have some test case to check this filter:
post the correct signature by using RestTemplate
post the wrong signature by using RestTemplate
post the correct signature by using MockMvc
post the wrong signature by using MockMvc
when I run that, case 1,2,3 were correct. but the case 4 has some problem.
I found that case 4 has run this code, but it did not redirect to my error controller.
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
Whether I used the default MockMvc by #Autowired or used the MockMvcBuilders to create, this code did not work.
So, I confuse how much different about the MockMvc and RestTemplate, and is RequestDispatcher possible to work by using MockMvc?
The filter code:
#Component
public class ApiFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(ApiFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
String requestBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
LOG.info("request id [{}] requset body [{}]", requestId, requestBody);
ApiRequestWrapper requestWrapper = null;
ApiResponseWrapper responseWrapper = null;
try {
requestWrapper = new ApiRequestWrapper(requestId, request, requestBody);
responseWrapper = new ApiResponseWrapper(requestId, response);
System.out.println(request.getRequestURI());
System.out.println(request.getRequestURL());
System.out.println(request.getPathInfo());
System.out.println(getServletContext());
if (StringUtils.equalsIgnoreCase(request.getHeader(ApiConstant.HEAD_SIGN), DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN))) {
filterChain.doFilter(requestWrapper, responseWrapper);
} else {
// redirect to error response
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
}
} finally {
LOG.info("request id [{}] response body [{}]", requestId, responseWrapper);
}
}
}
and my test case like this.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class LoginContorllerTest {
#Autowired ObjectMapper objectMapper;
//#Autowired
MockMvc mockMvc;
#Autowired TestRestTemplate restTemplate;
#Autowired WebApplicationContext webApplicationContext;
#Autowired ApiFilter apiFilter;
#Before
public void init() throws Exception {
MockFilterConfig filterConfig = new MockFilterConfig(webApplicationContext.getServletContext(), "apiFilter");
apiFilter.init(filterConfig);
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(apiFilter, "/api/*").build();
}
#Test
public void mockTest() throws Exception {
System.out.println("mockTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, sign).contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// please forgive me using this way in the demo
System.out.println(contentAsString);
System.out.println();
}
#Test
public void mockSignErrorTest() throws Exception {
System.out.println("mockSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, "12254548858").contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andReturn()
.getResponse()
.getContentAsString();
System.out.println(contentAsString);
System.out.println();
}
#Test
public void restTest() throws Exception {
System.out.println("restTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, sign);
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
#Test
public void restSignErrorTest() throws Exception {
System.out.println("restSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, "123456789");
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
}
Related
I'm a beginner and i'm writing unittests and I've stumbled across something I can't find a solution for that fits my needs.
I want to write some Junit Test for that exceptions.
There is my class with my Method
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
public final ResponseEntity<AccessError> numberFormatExceptionNotFoundException(
MethodArgumentTypeMismatchException ex, NumberFormatException exe, WebRequest request) {
AccessError errorDetails = new AccessError();
errorDetails.code("400");
errorDetails.addErrorsItem(new Error("400",ex.getMessage()));
errorDetails.setCode("400");
errorDetails.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errorDetails.setMessage(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorDetails.setPath(((ServletWebRequest) request).getRequest().getRequestURI());
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
AccessError errorDetails = new AccessError();
errorDetails.code("400");
errorDetails.addErrorsItem(new Error("400","Media Type Not Supported Exception"));
errorDetails.setCode("400");
errorDetails.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errorDetails.setMessage(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorDetails.setPath(((ServletWebRequest) request).getRequest().getRequestURI());
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
And there is my testClass :
public class CustomizedResponseEntityExceptionHandlerTest {
#Mock
ResponseEntity<AccessError> responseEntity;
WebRequest webRequest;
#InjectMocks
private CustomizedResponseEntityExceptionHandler custom = new CustomizedResponseEntityExceptionHandler();
#Test
public void numberFormatExceptionNotFoundExceptionTest() {
WebRequest webRequest;
String msg = "toto";
AccessError errors = new AccessError();
errors.setPath("app");
errors.getPath();
errors.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errors.timestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
ApiException apiException = new ApiException(errors, msg);
ResponseEntity<AccessError> responseApi = custom.handleUserNotFoundException(apiException, webRequest.getHeaderNames());
assertThatExceptionOfType(ApiException.class);
}
My Question is : How i can do a JUnit Test for that cases, which have webRequest and some exceptions ?
I've tried a lot of thing but i think i don't have the right thinking method.
Thanks !!
I found the solution
private CustomizedResponseEntityExceptionHandler test = new CustomizedResponseEntityExceptionHandler();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
#Test
public void numberFormatExceptionNotFoundExceptionTest() {
MethodArgumentTypeMismatchException expt = null ;
NumberFormatException exe = null;
servletRequest.setServerName("www.example.com");
servletRequest.setRequestURI("/v1/someuri");
servletRequest.addParameter("brand1", "value1");
servletRequest.addParameter("brand2", "value2");
WebRequest webRequest = new ServletWebRequest(servletRequest);
ResponseEntity<AccessError> result = test.numberFormatExceptionNotFoundException(expt,exe, webRequest);
assertNotNull(result);
}
You can use MockMvc to test your API for success/failure or any other custom response such as 404 Not found
A Sample snippet for example would look like this
public class MyTestClass{
#Autowired
private MockMvc mvc;
#Test
public void testMethod() {
MvcResult result = mvc.perform(post("/yourEndPoint")
.contentType("application/json") //Optional depending on your API Design
.content(content)) //Optional depending on your API Design
.andExpect(status().isOk()) //isOk , isBadRequest() and so on
.andReturn();
}
} //End of class
Refer this article which explains in a simple manner
https://howtodoinjava.com/spring-boot2/testing/spring-boot-mockmvc-example/
I have a Java project and I'm using Servlet in order to handle http requests.
I also using Spring
When I receive a request to create a new object (for example an account), I would like also to return the “location” header with the GET URL of the newly created object.
for example: location: /accounts/1000
I understand the header are added to the Servlet filter (correct me if Im wrong)
public class ApiLogFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger("apilogger");
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = ((HttpServletResponse) servletResponse);
httpServletResponse.addHeader( "Location","the location value");
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
String queryString = httpServletRequest.getQueryString() != null ? httpServletRequest.getQueryString() : "N/A";
String logMessage = "URL: " + httpServletRequest.getRequestURL() + ", Query String: " + queryString + ", Response Status: " + httpServletResponse.getStatus() ;
LOGGER.info(logMessage);
}
}
#Override
public void destroy() {
}
}
But I don't understand how to get the location value from the API
#RequestMapping("/accounts")
public class IgnoreRuleController {
private AccountService accountService;
public void setIgnoreRuleService(IgnoreRuleService ignoreRuleService) {
this.accountService = ignoreRuleService;
}
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestBody Account account) {
return new Gson().toJson(accountService.createAccount(account));
}
}
I found solution here
http://learningviacode.blogspot.com/2013/07/post-with-location.html
you didn't need to do anything with the filter.
in the api itself:
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> createIgnoreRule(#RequestBody IgnoreRule ignoreRule) {
String response = new Gson().toJson(ignoreRuleService.createIgnoreRule(ignoreRule));
final URI location = ServletUriComponentsBuilder
.fromCurrentServletMapping().path("/ignore_rules/{id}").build()
.expand(ignoreRule.getId()).toUri();
final HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
final ResponseEntity<String> entity = new ResponseEntity<>(response, headers, HttpStatus.CREATED);
return entity;
}
It's very simple, you can pass the header directly throw your method signature:
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestHeader HttpHeaders httpHeader, #RequestBody Account account) {
var s = httpHeader.get("Location");
System.out.println(s.get(0));
return ...
}
In fact you can pass the whole request also which contains everything (Headers, Body, ...):
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(HttpServletRequest httpRequest, #RequestBody Account account) {
var s = httpRequest.getHeader("Location");
System.out.println(s);
return ....
}
first of all I try to write unit-test for remote service to get weather and using restTemplate then implement ClientHttpRequestInterceptor to get remote request && remote response for logging purpose now i try to mock restTemplate to write unit-test and get the following error
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
and here you here code i wrote for unit-test
#RunWith(MockitoJUnitRunner.class)
public class WeatherRemoteServiceTest {
#Mock CustomResttemplate restTemplate;
#Mock WeatherRemoteUtilies weatherUtilies;
#InjectMocks WeatherRemoteService weatherRemote;
#Test
public void testRetrieveWeather() {
ResponseEntity<String> MockResponse= new ResponseEntity<String>(HttpStatus.OK);
Mockito.when(weatherUtilies.buildRequestParams(Mockito.anyString()))
.thenReturn(Mockito.any(MultiValueMap.class));
ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.OK);
Mockito.when(restTemplate.buildRestTemplate().exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<String>> any()
)
).thenReturn(responseEntity);
assertEquals(weatherRemote.retrieveWeather("ciaro").getStatusCode(), HttpStatus.OK);
}
}
and here is code for business logic itself
#Service
public class WeatherRemoteService {
private final Logger logger= LoggerFactory.getLogger(this.getClass());
#Value("${openweather.url}")
private String url;
#Autowired
private WeatherRemoteUtilies weatherUtilies;
#Autowired
private CustomResttemplate customRestTemplate;
public ResponseEntity<?> retrieveWeather(String city) {
logger.info(Constants.CLASS_NAME+this.getClass().getName()+Constants.METHOD_NAME+new Object() {}.getClass().getEnclosingMethod().getName());
logger.debug(Constants.METHOD_ARGUMENTS+city);
RestTemplate restRequest= customRestTemplate.buildRestTemplate();
HttpHeaders headers= new HttpHeaders();
headers.set("Accept",MediaType.APPLICATION_JSON_UTF8_VALUE);
UriComponentsBuilder uri= UriComponentsBuilder.fromUriString(url).
path("/data/2.5/weather")
.queryParams(weatherUtilies.buildRequestParams(city));
HttpEntity<String>entity= new HttpEntity<>(headers);
ResponseEntity<String>WeatherResponse=restRequest.exchange(uri.toUriString(), HttpMethod.GET, entity, String.class);
logger.info(Constants.END_METHOD);
return WeatherResponse;
}
}
code for RestTemplateInterceptor
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final Logger logger =LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse httpResponse= execution.execute(request, body);
logResponse(httpResponse);
return httpResponse;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
logger.info(Constants.START_REMOTE_REQUEST);
logger.info("URI :{}", request.getURI().toString());
logger.info("Method :{}", request.getMethod().toString());
logger.info("Headers :{}", request.getHeaders().toString());
logger.info("Request body :{}", new String(body,"UTF-8").toString());
logger.info(Constants.END_REMOTE_REQUEST);
}
private void logResponse(ClientHttpResponse response) throws IOException {
logger.info(Constants.START_REMOTE_RESPONSE);
logger.info("Status code :{}", response.getStatusCode().toString());
logger.info("Status text :{}", response.getStatusText().toString());
logger.info("Headers :{}", response.getHeaders().toString());
logger.info("Response body :{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
logger.info(Constants.END_REMOTE_RESPONSE);
}
}
then setInterceptor on RestTemplate factory using the following code
public class CustomResttemplate {
public RestTemplate buildRestTemplate() {
SimpleClientHttpRequestFactory simpleFactory= new SimpleClientHttpRequestFactory();
simpleFactory.setOutputStreaming(false);
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(simpleFactory);
RestTemplate restTemplate= new RestTemplate(factory);
restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor()));
return restTemplate;
}
}
Your problem is where you write
.thenReturn(Mockito.any(MultiValueMap.class))
You actually have to tell Mockito what to return. You can't use a matcher here. Matchers are for verifying, and for setting up what conditions to stub. You can't use them to tell Mockito what to return from a stubbed call.
Create a specific MultiValueMap to pass to thenReturn.
I am currently writing unit test for below method
#Autowired
private RequestConfig requestConfig;
#Autowired
private RetryTemplate retryTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
UPDATED METHOD:
#Autowired
private RequestConfig requestConfig;
#Autowired
private RetryTemplate retryTemplate;
#Autowired
private RestTemplate restTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
I tried all possibilities but I am unable to get it right. Here is my below test.
#Mock
private RestTemplate restTemplate;
#Mock
public RequestConfig requestConfig;
#InjectMocks
private RetryTemplate retryTemplate;
ServiceRequest serviceRequest;
#Test
public void makeGetServiceCall() throws Exception {
String url = "http://localhost:8080";
RetryTemplate mockRetryTemplate = Mockito.mock(RetryTemplate.class);
RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class);
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
Mockito.when(mockRetryTemplate.execute(ArgumentMatchers.any(RetryCallback.class), ArgumentMatchers.any(RecoveryCallback.class), ArgumentMatchers.any(RetryState.class))).thenReturn(myEntity);
Mockito.when(mockRestTemplate.exchange(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(HttpMethod.GET),
ArgumentMatchers.<HttpEntity<String>>any(),
ArgumentMatchers.<Class<String>>any())
).thenReturn(myEntity);
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
Assert.assertEquals(myEntity, response);
}
UPDATED TEST CASE:
#Mock
public RequestConfig requestConfig;
#Mock
private RestTemplate restTemplate;
#Mock
private RetryTemplate retryTemplate;
#InjectMocks
ServiceRequest serviceRequest;
#Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
when(requestConfig.createHttpHeaders()).thenReturn(null);
ResponseEntity<String> myEntity = new ResponseEntity<>( HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class), any(RecoveryCallback.class), any(RetryState.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
The response object which I get from my method call makeGetServiceCall always return null. When I debug the code I see exception org.mockito.exceptions.misusing.WrongTypeOfReturnValue: ResponseEntity cannot be returned by toString() toString() should return String error on the resttemplate mocking where I return myEntity
I am not sure what am I missing.
Well, you have made quite some number of mistakes...
I'm sure you wanted to annotate private RetryTemplate retryTemplate; with #Mock, not #InjectMocks
#InjectMocks should go onto ServiceRequest serviceRequest;
You are defining interactions on some mockRetryTemplate and mockRestTemplate which have nothing to do with serviceRequest. Instead, you should use your #Mock-annotated fields to define interactions on because they are being injected into your object under test (serviceRequest)
Moreover, you can't normally mock RestTemplate and inject it into your ServiceRequest because you don't use dependency injection in the first place for RestTemplate in ServiceRequest. You just instantiate its instance in ServiceRequest.makeGetServiceCall
You are defining an interaction on the wrong method at line Mockito.when(retryTemplate.execute(.... Your interaction specifies RetryTemplate.execute(RetryCallback, RecoveryCallback, RetryState) whereas your ServiceRequest uses another method RetryTemplate.execute(RetryCallback)
You should also notice that RetryTemplate.execute is final and so you can't mock it without extra efforts as explained here. And generally, you should prefer interfaces over classes, e.g. RestOperations and RetryOperations over RestTemplate and RetryTemplate respectively, to be more flexible.
That said, below is the working test which solves your problem. But take note of removing RestTemplate restTemplate = new RestTemplate(); from ServiceRequest and making restTemplate a field so it's dependency-injected.
#RunWith(MockitoJUnitRunner.class)
public class ServiceRequestTest {
#Mock
private RestTemplate restTemplate;
#Mock
public RequestConfig requestConfig;
#Mock
private RetryTemplate retryTemplate;
#InjectMocks
ServiceRequest serviceRequest;
#Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
}
For me below worked, otherwise, it was returning null always
when(retryTemplate.execute(any(),any(),any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
and import was
import static org.mockito.ArgumentMatchers.any;
Generic solution:
Mockito.when(retryTemplate.execute(Matchers.any(),Matchers.any(),Matchers.any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgumentAt(0,Matchers.any());
return retry.doWithRetry(null);
});
It works for me!
#ExtendWith(MockitoExtension.class)
class RetryableRestClientTest {
#Mock
private RestTemplate restTemplate;
#Mock
private RetryTemplate retryTemplate;
#InjectMocks
private RetryableRestClient client;
#SuppressWarnings("rawtypes")
#Test
void test_get() {
String url = "https://faked-url";
ResponseEntity<String> expectedResponseEntity = new ResponseEntity<>(HttpStatus.OK);
Mockito.when(retryTemplate.execute(Mockito.any(), Mockito.any(), Mockito.any()))
.thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
Mockito.when(restTemplate.exchange(Mockito.eq(url), Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(String.class)))
.thenReturn(expectedResponseEntity);
ResponseEntity<String> actualResponseEntity = client.get(url);
Assertions.assertEquals(expectedResponseEntity, actualResponseEntity);
}
}
#Component
public class RetryableRestClient {
#Autowired
private RetryTemplate retryTemplate;
#Autowired
private RestTemplate restTemplate;
private HttpHeaders fakeHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
// fake browser's behavior
headers.add("authority", "m.nowscore.com");
headers.add("cache-control", "max-age=0");
headers.add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"");
headers.add("sec-ch-ua-mobile", "?0");
headers.add("upgrade-insecure-requests", "1");
headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36");
headers.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
headers.add("sec-fetch-site", "none");
headers.add("sec-fetch-mode", "navigate");
headers.add("sec-fetch-user", "?1");
headers.add("sec-fetch-dest", "document");
headers.add("accept-language", "en-US,en;q=0.9");
return headers;
}
public final ResponseEntity<String> get(String url) {
return retryTemplate.execute(context -> restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, fakeHttpHeaders()), String.class));
}
}
Trying to test a spring controller that we have for multiple file upload. Here is the controller:
#RequestMapping("/vocabularys")
#Controller
public class VocabularyController {
...
The action I want to test:
#RequestMapping(value = "/import", method = {RequestMethod.PUT, RequestMethod.POST})
#ResponseBody
#CacheEvict(value="vocabulary", allEntries=true)
public Object importVocabulary(MultipartHttpServletRequest request, HttpServletResponse response) {
...
The resolver I have in the webmvc-config.xml:
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
The code works just fine and all. I'm running into problems when I am trying to unit/integration test this.
Here is my attempt at the test:
public class VocabularyControllerTest extends BaseControllerTest {
static final private String AdminUsername = "administrator";
#Test
public void shouldBeAbleToUploadAFile() throws Exception {
createTestWorkspace();
login(AdminUsername, "*");
MockMultipartFile file = new MockMultipartFile("test_vocab.xml", new FileInputStream("src/test/files/acme_vocabulary.xml"));
MockMultipartHttpServletRequestBuilder mockMultipartHttpServletRequestBuilder = (MockMultipartHttpServletRequestBuilder) fileUpload("/vocabularys/import").accept(MediaType.ALL).session(httpSession);
mockMultipartHttpServletRequestBuilder.file(file);
mockMultipartHttpServletRequestBuilder.content("whatever");
ResultActions resultActions = mockMvc.perform(mockMultipartHttpServletRequestBuilder);
resultActions.andExpect(status().isFound());
}
}
Ignore the createWorkspace() and login() and stuff - those are for passing through some security filters.
The relevant part of the BaseControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(locations = {
"file:src/test/resources/META-INF/spring/applicationContext.xml",
"file:src/test/resources/META-INF/spring/applicationContext-security.xml",
"file:src/main/resources/META-INF/spring/applicationContext-database.xml",
"file:src/main/resources/META-INF/spring/applicationContext-activiti.xml",
"file:src/main/resources/META-INF/spring/applicationContext-cache.xml",
"file:src/main/resources/META-INF/spring/applicationContext-jms.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mail.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mongo.xml"}),
#ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
"file:src/test/webapp/WEB-INF/spring/applicationContext-filters.xml"})
})
#Transactional
public class BaseControllerTest extends BaseTest {
#Autowired
WebApplicationContext wac;
#Autowired
MockHttpSession httpSession;
#Autowired
MockServletContext servletContext;
#Autowired
OpenEntityManagerInViewFilter openEntityManagerInViewFilter;
#Autowired
HiddenHttpMethodFilter hiddenHttpMethodFilter;
#Autowired
CharacterEncodingFilter characterEncodingFilter;
#Autowired
SessionFilter sessionFilter;
#Autowired
WorkflowAsSessionFilter workflowAsSessionFilter;
#Autowired
FilterChainProxy springSecurityFilterChain;
#Autowired
RequestFilter requestFilter;
MockMvc mockMvc;
protected static final String TestFileDir = "src/test/files/";
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(openEntityManagerInViewFilter, "/*")
.addFilter(hiddenHttpMethodFilter, "/*")
.addFilter(characterEncodingFilter, "/*")
.addFilter(sessionFilter, "/*")
.addFilter(workflowAsSessionFilter, "/*")
.addFilter(springSecurityFilterChain, "/*")
.addFilter(requestFilter, "/*")
.build();
servletContext.setContextPath("/");
Session session = Session.findBySessionId(httpSession.getId());
if (session == null) {
session = new Session();
session.setJsessionid(httpSession.getId());
session.persist();
}
}
...
The issue is that when I try debugging this, the perform action on the mockMvc object never hits my controller method. I thought it was an issue getting past our security filters (which is why I have all the login and stuff) but I tested other actions in the vocabulary controller and I am able to hit them just fine.
Thoughts? Ideas? Suggestions?
Alright, found the issue.
Spring's MockMultipartHttpServletRequestBuilder returns a MockHttpMultipartServletRequest object eventually.
What the browser does however is post a multipart-encoded request which then gets picked up and parsed by the CommonsMultipartResolver bean defined in the XML.
In the test however, since we are already posting a MockHttpMultipartServletRequest, we don't want the resolver parsing this, so all we got to do is have a profile where the resolver doesn't kick in.
What we have chosen to do however is end up constructing a MockHttpServletRequest that has multipart encoding and put it through the Spring filters so that we can also integration test the resolver kicking in.
Unfortunately I don't see any support/helper in the Spring testing lib which allows you to take a MockHttpServletRequest and addPart() to it, or something to that effect => handcoded browser emulation function :(
The simple way how to test multipart upload is use StandardServletMultipartResolver.
and for test use this code:
final MockPart profilePicture = new MockPart("profilePicture", "stview.jpg", "image/gif", "dsdsdsd".getBytes());
final MockPart userData = new MockPart("userData", "userData", "application/json", "{\"name\":\"test aida\"}".getBytes());
this.mockMvc.perform(
fileUpload("/endUsers/" + usr.getId().toString()).with(new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.addPart(profilePicture);
request.addPart(userData);
return request;
}
})
MockPart class
public class MockPart extends MockMultipartFile implements Part {
private Map<String, String> headers;
public MockPart(String name, byte[] content) {
super(name, content);
init();
}
public MockPart(String name, InputStream contentStream) throws IOException {
super(name, contentStream);
init();
}
public MockPart(String name, String originalFilename, String contentType, byte[] content) {
super(name, originalFilename, contentType, content);
init();
}
public MockPart(String name, String originalFilename, String contentType, InputStream contentStream) throws IOException {
super(name, originalFilename, contentType, contentStream);
init();
}
public void init() {
this.headers = new HashMap<String, String>();
if (getOriginalFilename() != null) {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"; filename=\"" + getOriginalFilename() + "\"");
} else {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"");
}
if (getContentType() != null) {
this.headers.put("Content-Type".toLowerCase(), getContentType());
}
}
#Override
public void write(String fileName) throws IOException {
}
#Override
public void delete() throws IOException {
}
#Override
public String getHeader(String name) {
return this.headers.get(name.toLowerCase());
}
#Override
public Collection<String> getHeaders(String name) {
List<String> res = new ArrayList<String>();
if (getHeader(name) != null) {
res.add(getHeader(name));
}
return res;
}
#Override
public Collection<String> getHeaderNames() {
return this.headers.keySet();
}
}