Nital
Nital

Reputation: 6114

Failing JUnit test for REST API in Spring Boot

I am getting an exception while running a JUnit test against a Spring Boot REST controller. I tested the API through Postman and it works as expected. Not sure what I am missing in JUnit test.

ProductController.java

@RestController
@RequestMapping("/api")
public class ProductController {

    @Inject
    private ProductRepository productRepository;

    //URI: http://localhost:8080/api/products/50
    @RequestMapping(value = "/products/{productId}", method = RequestMethod.GET)
    public ResponseEntity<?> getProduct(@PathVariable Long productId) {
        verifyProductExists(productId);
        Product product = productRepository.findOne(productId);
        return new ResponseEntity<>(product, HttpStatus.OK);
    }

    protected void verifyProductExists(Long productId) throws ResourceNotFoundException {
        Product product = productRepository.findOne(productId);
        if (product == null) {
            throw new ResourceNotFoundException("Product with id " + productId + " not found...");
        }
    }

}

ResourceNotFoundException.java

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException() {
    }

    public ResourceNotFoundException(String message) {
        super(message);
    }

    public ResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

}

Through Postman:

http://localhost:8080/api/products/1 -> Returns 200 with Product data in JSON format
http://localhost:8080/api/products/999 -> Returns 404 with Exception data in JSON format

ProductRestClientTest.java

@RunWith(SpringJUnit4ClassRunner.class)
public class ProductRestClientTest {

    static final String VALID_PRODUCT_API_URI = "http://localhost:8080/api/products/35";
    static final String INVALID_PRODUCTS_API_URI = "http://localhost:8080/api/products/555";
    private RestTemplate restTemplate;

    @Before
    public void setUp() {
        restTemplate = new RestTemplate();
    }

    /*
    Testing Happy Path scenario
     */
    @Test
    public void testProductFound() {
        ResponseEntity<?> responseEntity = restTemplate.getForEntity(VALID_PRODUCT_API_URI, Product.class);
        assert (responseEntity.getStatusCode() == HttpStatus.OK);
    }

    /*
    Testing Error scenario
     */
    @Test(expected = ResourceNotFoundException.class)
    public void testProductNotFound() {
        ResponseEntity<?> responseEntity = restTemplate.getForEntity(INVALID_PRODUCTS_API_URI, Product.class);
        assert (responseEntity.getStatusCode() == HttpStatus.NOT_FOUND);
    }

    @After
    public void tearDown() {
        restTemplate = null;
    }

}

Exception while running above JUnit test

Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.759 sec <<< FAILURE! - in com.study.spring.boot.rest.ProductRestClientTest
testProductNotFound(com.study.spring.boot.rest.ProductRestClientTest)  Time elapsed: 0.46 sec  <<< ERROR!
java.lang.Exception: Unexpected exception, expected<com.study.spring.boot.rest.ResourceNotFoundException> but was<org.springframework.web.client.HttpClientErrorException>
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:312)
    at com.study.spring.boot.rest.ProductRestClientTest.testProductNotFound(ProductRestClientTest.java:42)

Upvotes: 2

Views: 7206

Answers (2)

Kevin Peters
Kevin Peters

Reputation: 3474

The problem with the test is that with the 404 response of the RestTemplate the DefaultResponseErrorHandler method handleError(ClientHttpResponse response) is triggered.

In your case (returning your 404 status code -> client error) it causes a HttpClientErrorException:

HttpStatus statusCode = getHttpStatusCode(response);
    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, response.getStatusText(),
                    response.getHeaders(), getResponseBody(response), getCharset(response));

There are at least two solutions for that:

Either disabling the default error handling in your tests, maybe enhance your setUp() method like:

   restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
        protected boolean hasError(HttpStatus statusCode) {
            return false;
        }});

And remove the (expected = ResourceNotFoundException.class) clause from your negative test. Because asserting 404 after getting the response and expecting a exception won't work together.

Or use MockMvc. It offers even more sophisticated stuff and skips the DefaultResponseErrorHandler per default.

For example your test could look like this:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
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.MockHttpServletRequestBuilder;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class ProductRestClientTestWithMockMvc {

    private static final String PRODUCT_API_URI = "http://localhost:8080/api/products/{productId}";
    private MockMvc mockMvc = null;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void before() throws Exception {
        mockMvc = webAppContextSetup(webApplicationContext).build();
    }

    @After
    public void after() throws Exception {
        mockMvc = null;
    }

    /*
     * Testing Happy Path scenario
     */
    @Test
    public void testProductFound() throws Exception {
        final MockHttpServletRequestBuilder builder = get(PRODUCT_API_URI, 35);
        final ResultActions result = mockMvc.perform(builder);
        result.andExpect(status().isOk());
    }

    /*
     * Testing Error scenario
     */
    @Test
    public void testProductNotFound() throws Exception {
        final MockHttpServletRequestBuilder builder = get(PRODUCT_API_URI, 555);
        final ResultActions result = mockMvc.perform(builder);
        result.andExpect(status().isNotFound());
    }

}

Upvotes: 3

Pirulino
Pirulino

Reputation: 768

The point is that you are not returning an exception but an http message that contains a body and a http status code. In this case you get a 404 code, but no one translates this code in an exception. In order the get the wanted exception you need to instruct the restTemplate to throw the ResourceNotFoundException when a 404 is met. Basically you need an error handler:

RestTemplate restclient = new RestTemplate(); restclient.setErrorHandler(new MyResponseErrorHandler());

Hope that helps.

Upvotes: 0

Related Questions