You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Mock RestTemplate测试返回HTTP 500问题排查

问题描述

我有一个Spring Boot的Controller类:

package com.tsdevelopment.springbootrest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.List;

@RestController
public class SpringController {
    private List<String> myList = Arrays.asList("Item 1", "Item 2");

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/v1/resources")
    public ResponseEntity<List<String>> getAllResources(@RequestParam(required = false) String parameter){
        return ResponseEntity.ok(myList);
    }
    
    //API to API call
    @GetMapping("/v1/resources/inter-api-call")
    public ResponseEntity<List<String>> apiCall(){
        List<String> apiResponse = restTemplate.getForEntity("http://localhost:8083/v1/resources", List.class).getBody();
        return ResponseEntity.ok(apiResponse);
    }

}

对应的测试类如下:

package com.tsdevelopment.springbootrest;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matchers;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringControllerTest {
    @InjectMocks
    SpringController springController;

    @Mock
    RestTemplate restTemplate;

    @LocalServerPort
    private int port;

    @BeforeEach
    public void setUp(){
        RestAssured.port = port;
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void apiCall(){
        Mockito.when(restTemplate.getForEntity("http://localhost:8083/v1/resources", List.class)).thenReturn(ResponseEntity.ok(Arrays.asList("Item 1", "Item 2")));
        System.out.println("RestTemplate response: " + restTemplate.getForEntity("http://localhost:8083/v1/resources", List.class));
        System.out.println("Controller response: " + springController.apiCall());
        RestAssured.given()
            .when()
            .get("/v1/resources/inter-api-call")
            .then()
            .statusCode(200)
            .body("", Matchers.equalTo("[\"Item 1\",\"Item 2\"]"));
    }

}

执行测试时失败,控制台打印的Mock调用和直接调用Controller的结果都是正常的:

RestTemplate response: <200 OK OK,[Item 1, Item 2],[]>
Controller response: <200 OK OK,[Item 1, Item 2],[]>
但测试报错:
java.lang.AssertionError: 1 expectation failed.
Expected status code <200> but was <500>.

问题原因

你用@SpringBootTest启动了完整的Spring容器,但@Mock和@InjectMocks只是在测试类内部创建了一个被Mock注入的SpringController实例,容器中的SpringController实例并没有使用你Mock的RestTemplate。当RestAssured调用接口时,实际请求的是容器里的Controller,它的RestTemplate是真实的Bean,会去调用http://localhost:8083的真实服务,而这个服务大概率未启动,所以抛出500错误。

解决方案

有两种方式可以解决这个问题:

方式一:使用@MockBean替代@Mock和@InjectMocks

@MockBean会把Mock的RestTemplate注入到Spring容器中,这样容器里的SpringController就会使用这个Mock实例:
修改测试类如下:

package com.tsdevelopment.springbootrest;

import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.server.LocalServerPort;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matchers;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringControllerTest {

    @MockBean // 替换@Mock和@InjectMocks
    RestTemplate restTemplate;

    @LocalServerPort
    private int port;

    @BeforeEach
    public void setUp(){
        RestAssured.port = port;
    }

    @Test
    public void apiCall(){
        Mockito.when(restTemplate.getForEntity("http://localhost:8083/v1/resources", List.class))
               .thenReturn(ResponseEntity.ok(Arrays.asList("Item 1", "Item 2")));
        
        RestAssured.given()
            .when()
            .get("/v1/resources/inter-api-call")
            .then()
            .statusCode(200)
            .body("", Matchers.equalTo("[\"Item 1\",\"Item 2\"]"));
    }

}

方式二:使用@WebMvcTest做切片测试

如果你只需要测试Controller层,不需要启动完整容器,可以用@WebMvcTest,配合@MockBean来Mock依赖:

package com.tsdevelopment.springbootrest;

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matchers;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

@WebMvcTest(SpringController.class)
public class SpringControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    RestTemplate restTemplate;

    @BeforeEach
    public void setUp(){
        RestAssuredMockMvc.mockMvc(mockMvc);
    }

    @Test
    public void apiCall(){
        Mockito.when(restTemplate.getForEntity("http://localhost:8083/v1/resources", List.class))
               .thenReturn(ResponseEntity.ok(Arrays.asList("Item 1", "Item 2")));
        
        RestAssuredMockMvc.given()
            .when()
            .get("/v1/resources/inter-api-call")
            .then()
            .statusCode(200)
            .body("", Matchers.equalTo("[\"Item 1\",\"Item 2\"]"));
    }

}

注意这种方式需要引入rest-assured-spring-mock-mvc依赖。

内容的提问来源于stack exchange,提问作者TSSK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:22:03