Java Spring引导中REST API的JUnit测试失败

Java Spring引导中REST API的JUnit测试失败,java,spring,spring-boot,junit,spring-restcontroller,Java,Spring,Spring Boot,Junit,Spring Restcontroller,我在对Spring Boot REST控制器运行JUnit测试时遇到异常。我通过Postman对API进行了测试,结果与预期一致。不确定我在JUnit测试中遗漏了什么 ProductController.java @RestController @RequestMapping("/api") public class ProductController { @Inject private ProductRepository productRepository; //U

我在对Spring Boot REST控制器运行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...");
        }
    }

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

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

}
通过邮递员:

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

@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...");
        }
    }

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

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

}
@RunWith(SpringJUnit4ClassRunner.class)
公共类ProductRestClientTest{
静态最终字符串有效\u产品\u API\u URI=”http://localhost:8080/api/products/35";
静态最终字符串无效\u产品\u API\u URI=”http://localhost:8080/api/products/555";
私有RestTemplate RestTemplate;
@以前
公共作废设置(){
restTemplate=新的restTemplate();
}
/*
测试快乐路径场景
*/
@试验
public void testProductFound(){
ResponseEntity ResponseEntity=restemplate.getForEntity(有效的产品API URI、产品类);
断言(responseEntity.getStatusCode()==HttpStatus.OK);
}
/*
测试错误场景
*/
@测试(预期=ResourceNotFoundException.class)
public void testProductNotFound(){
ResponseEntity ResponseEntity=restTemplate.getForEntity(无效的\u产品\u API\u URI,Product.class);
断言(responseEntity.getStatusCode()==HttpStatus.NOT_FOUND);
}
@之后
公共无效拆卸(){
restemplate=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)

测试运行:2,失败:0,错误:1,跳过:0,经过的时间:0.759秒关键是您没有返回异常,而是返回一条包含正文和http状态代码的http消息。在本例中,您将获得404代码,但没有人在异常中翻译此代码。
为了获得想要的异常,需要指示restTemplate在遇到404时抛出ResourceNotFoundException。
基本上,您需要一个错误处理程序:

RestTemplate restclient=新的RestTemplate(); setErrorHandler(新的MyResponseErrorHandler())


希望这会有所帮助。

测试的问题是,使用
restemplate
的404响应会触发
DefaultResponseErrorHandler
方法
handleError(ClientHttpResponse响应)

在您的情况下(返回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和期望异常不会一起工作

或使用。它提供了更复杂的东西,并根据默认值跳过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());
    }

}

您是否尝试使用@IntegrationTest注释您的测试类?
MyResponseErrorHandler
是否是我需要创建的新类?是的。它必须实施