java - Spring Boot 中 REST API 的 JUnit 测试失败

标签 java spring spring-boot junit spring-restcontroller

我在针对 Spring Boot REST Controller 运行 JUnit 测试时遇到异常。我通过 Postman 测试了 API,它按预期工作。不确定 JUnit 测试中缺少什么。

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);
    }

}

通过 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;
    }

}

在 JUnit 测试之上运行时出现异常

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)

最佳答案

测试的问题在于,RestTemplate 的 404 响应会触发 DefaultResponseErrorHandler 方法 handleError(ClientHttpResponse response)

在您的情况下(返回 404 状态代码 -> 客户端错误),它会导致 HttpClientErrorException:

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

至少有两种解决方案:

要么在测试中禁用默认错误处理,要么增强您的 setUp() 方法,例如:

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

并从否定测试中删除 (expected = ResourceNotFoundException.class) 子句。因为在收到响应后断言 404 和期待异常不会一起工作。

或者使用MockMvc 。它提供了更复杂的东西,并默认跳过 DefaultResponseErrorHandler。

例如,您的测试可能如下所示:

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());
    }

}

关于java - Spring Boot 中 REST API 的 JUnit 测试失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42209410/

相关文章:

java - 防止 Volley 同时发送相同的请求

java.lang.NoClassDefFoundError——卡住

java - JAXB 的 @XmlElement(name ="") 在 Mule 中不起作用

带有日期的 Spring MVC 输入标记引发错误 : HTTP Status 400 -The request sent by the client was syntactically incorrect

java - Spring Boot :org. springframework.beans.factory.UnsatisfiedDependencyException

java - 为什么 Joshua Bloch 在 Effective Java 中使用 2*size + 1 来调整堆栈大小?

java - 如何在 JUnit 测试初始化​​时模拟 Bean 所需的文件

java - Spring 数据 : DeleteAll and Insert in same transaction

java - Springboot : Ignore null values in response optionally

java - 如何从javadocs生成Swagger UI?