Spring RestTemplate invoking webservice with errors and analyze status code
Asked Answered
N

5

31

I designed a webservice to perform a task if request parameters are OK, or return 401 Unauthorized HTTP status code if request parameters are wrong or empty.

I'm using RestTemplate to perform a test and I'm able to verify the HTTP 200 OK status if the webservice replies with success. I am however unable to test for HTTP 401 error because RestTemplate itself throws an exception.

My test method is

@Test
public void testUnauthorized()
{
    Map<String, Object> params = new HashMap<String, Object>();
    ResponseEntity response = restTemplate.postForEntity(url, params, Map.class);
    Assert.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    Assert.assertNotNull(response.getBody());
}

Exception log is

org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:88)
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:533)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:489)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:447)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:318)

How I can test if webservice replies with a HTTP status code 401?

Naturism answered 14/3, 2013 at 8:44 Comment(1)
Did my answer work for you?Hearty
H
50

You need to implement ResponseErrorHandler in order to intercept response code, body, and header when you get non-2xx response codes from the service using rest template. Copy all the information you need, attach it to your custom exception and throw it so that you can catch it in your test.

public class CustomResponseErrorHandler implements ResponseErrorHandler {

    private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();

    public boolean hasError(ClientHttpResponse response) throws IOException {
        return errorHandler.hasError(response);
    }

    public void handleError(ClientHttpResponse response) throws IOException {
        String theString = IOUtils.toString(response.getBody());
        CustomException exception = new CustomException();
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("code", response.getStatusCode().toString());
        properties.put("body", theString);
        properties.put("header", response.getHeaders());
        exception.setProperties(properties);
        throw exception;
    }
}

Now what you need to do in your test is, set this ResponseErrorHandler in RestTemplate like,

RestTemplate restclient = new RestTemplate();
restclient.setErrorHandler(new CustomResponseErrorHandler());
try {
    POJO pojo = restclient.getForObject(url, POJO.class); 
} catch (CustomException e) {
    Assert.isTrue(e.getProperties().get("body")
                    .equals("bad response"));
    Assert.isTrue(e.getProperties().get("code").equals("400"));
    Assert.isTrue(((HttpHeaders) e.getProperties().get("header"))
                    .get("fancyheader").toString().equals("[nilesh]"));
}
Hearty answered 28/3, 2013 at 15:33 Comment(9)
Do note that if you follow this pattern, CustomException is a unchecked exception. You can extend RuntimeException, for example. Otherwise, the exception can't get passed the "throws IOException" of that method.Cyclonite
Note, I'm using a post requestStormie
@snowe2010 You could do the following to get your HttpCustomException. ------> catch (Exception e){ if(e.getCause() instanceof HttpCustomException){ HttpCustomException customException= (HttpCustomException)e.getCause(); l.warn("Custom exception for Rest template"); } }Darcydarda
@snowe2010 is this a compilation error? Or run time error? Remember you are extending a checked exception like IOException. It must be handled carefully by callers. You can always extend RuntimeException instead but I don't know your use caseHearty
@Hearty it was a compilation error. I ended up taking a different route and not using RestTemplate because I figured out how to use MockMvc properly.Stormie
Hmm that sounds like a checked exception was not handled correctly.Hearty
You can use TestRestTemplate, your original code should work out of the boxSwelling
I am having this problem. Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/io/IOUtils. It is so weird. I have the maven entry and allInna
Is there a clean way of converting the response.getBody() inputsstream to an objects other than converting it to a string and then to an object?Morganatic
E
31

As an alternative to the solution presented by nilesh, you could also use spring class DefaultResponseErrorHandler. You also need to ovveride its hasError(HttpStatus) method so it does not throw exception on non-successful result.

restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
    protected boolean hasError(HttpStatus statusCode) {
        return false;
    }});
Eclecticism answered 18/11, 2014 at 9:23 Comment(1)
This worked really well for me. Because the API I use returns an JSON error, the thrown exception was prohibiting me to see it. This gave me enough flexibility to debug.Kami
P
7

In my rest service I catch HttpStatusCodeException instead of Exception since HttpStatusCodeException has a method for getting the status code

catch(HttpStatusCodeException e) {
    log.debug("Status Code", e.getStatusCode());
}
Pulpboard answered 22/1, 2016 at 8:24 Comment(2)
for a fast, quick solution, this works well, and if you need to access the response content, you can just catch HttpClientErrorException and/or the HttpServerErrorException, both of which have the getResponseBodyAsString() methodCharles
My two cents: if you're using the TestRestTemplate class (Spring Boot), you should use catch( RestClientException ).Calisa
F
5

You can use spring-test. It's much easier:

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:your-context.xml")
public class BasicControllerTest {

        @Autowired
        protected WebApplicationContext wac;
        protected MockMvc mockMvc;

        @Before
        public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        }

        @Test
        public void testUnauthorized(){

        mockMvc.perform(MockMvcRequestBuilders
                            .post("your_url")
                            .param("name", "values")
        .andDo(MockMvcResultHandlers.print())
        .andExpect(MockMvcResultMatchers.status().isUnauthorized()
        .andExpect(MockMvcResultMatchers.content().string(Matchers.notNullValue()));
        }
}
Flannel answered 14/3, 2013 at 9:55 Comment(1)
This doesn't work if you need to add authentication to your headers as spring does not run tests through filters by default.Stormie
D
4

Since Spring 4.3, There is a RestClientResponseException which contain actual HTTP response data, such as status code, response body and headers. And you can catch it.

RestClientResponseException Java Doc

Dinh answered 5/9, 2016 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.