八种测试方法,让 Spring Boot 接口稳定性提升 200%

进行系统化的 Controller 接口测试至关重要。它不仅能验证 API 的正确性、保障数据交互的准确性,还能提前暴露集成缺陷,确保在各种边界和异常场景下系统行为符合预期,是提升软件质量、支撑持续交付的关键环节。

环境:SpringBoot3.4.2

1. 简介

在前后端分离架构中,Controller 作为系统的 "门面",承担着接收请求、处理参数、调用业务逻辑并返回响应的核心职责。其接口的稳定性直接关系到整个系统的可用性与用户体验。

因此,进行系统化的 Controller 接口测试至关重要。它不仅能验证 API 的正确性、保障数据交互的准确性,还能提前暴露集成缺陷,确保在各种边界和异常场景下系统行为符合预期,是提升软件质量、支撑持续交付的关键环节。

通常Controller层主要做如下的验证:

  • 请求映射
    验证 HTTP 方法(GET/POST 等)、路径(/users/{id})是否正确映射到目标方法。
  • 请求/响应的序列化与反序列化
    确保 JSON 与 Java 对象之间的转换(@RequestBody, @ResponseBody)准确无误。
  • 验证、异常处理与 HTTP 状态码
    测试数据校验(@Valid)、全局异常处理器(@ControllerAdvice)能否正确返回 400、404、500 等状态码。
  • 与服务层的集成
    验证 Controller 是否正确调用 Service 方法,参数传递无误。可通过 @MockBean 模拟依赖,或连接真实服务进行测试。

2.实战案例

准备环境

public record User(Long id, String name, Integer age) {
}
@Service
public class UserService {
  public User queryUser(Long id) {
    return new User(id, "Pack_xg", 33) ;
  }


  public List<User> queryUsers() {
    return List.of(new User(1L, "pack", 33)) ;
  }


  public User save(User user) {
    System.err.println("创建用户...") ;
    return user ;
  }
}
// Controller接口
@RestController
@RequestMapping("/users")
public class UserController {
  private final UserService userService ;
  public UserController(UserService userService) {
    this.userService = userService ;
  }
  @GetMapping("/{id}")
  public User queryUser(@PathVariable Long id) {
    return this.userService.queryUser(id);
  }


  @GetMapping("")
  public List<User> queryUsers() {
    return this.userService.queryUsers() ;
  }


  @PostMapping("")
  @ResponseStatus(code = HttpStatus.CREATED)
  public User save(@RequestBody User user) {
    return user ;
  }
}

接下来,我们将通过8种方法对上面的接口进行不同场景下的测试。

2.1 使用 @WebMvcTest

目的

该方法通过模拟所有依赖项(如服务层、数据访问层),实现对控制器层的独立隔离测试。

你可以验证以下关键点:

  • ✅ URL 映射:例如,GET /users 是否正确调用了对应的处理方法。
  • ✅ 请求/响应处理:请求参数和请求体能否正确反序列化,响应对象能否正确序列化为 JSON 或 XML。
  • ✅ HTTP 状态码与错误响应:正常请求返回 200 OK,资源未找到返回 404 Not Found,参数校验失败返回 400 Bad Request 等。
  • ✅ 与模拟服务的交互:控制器是否按预期调用了服务层方法,传递的参数是否正确,行为是否符合设计。

核心注解&类说明:

  • @WebMvcTest(YourController.class) :Spring Boot 仅加载 Web 层(如控制器、拦截器、消息转换器等),不会启动完整的应用上下文,从而实现快速、专注的测试。
  • MockMvc :用于模拟 HTTP 请求(如 GET、POST),并验证响应(如状态码、响应体内容),无需启动真实服务器。
  • @MockBean: 将应用上下文中真实的依赖服务(例如 UserService)替换为 Mockito 模拟对象,从而隔离外部依赖,精准控制和验证服务调用行为。

测试用例:

@WebMvcTest(UserController.class)
public class UserControllerTest1 {
  // 用于模拟 HTTP 请求。
  @Resource
  private MockMvc mockMvc;
  // 被模拟的服务(真实的服务被忽略)。
  @MockitoBean
  private UserService userService;
  @Test
  public void testUsers() throws Exception {
    // 1.设置模拟行为
    when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))) ;
    // 2.模拟 HTTP GET 请求到 /users 并验证响应
    mockMvc.perform(get("/users"))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].name").value("pack"));
  }
  @Test
  public void testUser() throws Exception {
    when(userService.queryUser(1L))
      .thenThrow(new UserNotFoundException());
    mockMvc.perform(get("/users/1"))
      .andExpect(status().isNotFound());
  }
}

✅ 优点

  • ✔ 执行速度快:无需连接数据库或进行真实网络调用,测试运行迅速。
  • ✔ 隔离控制器逻辑:排除服务层、数据层等外部干扰,精准验证控制器自身行为。
  • ✔ 适合验证请求/响应格式:可有效测试 URL 映射、参数绑定、JSON 序列化、状态码及错误响应结构。

⚠ 缺点

  • ❌ 不测试真实的服务/数据层集成:由于依赖被模拟,无法发现真实调用中的集成问题。
  • ❌ 对 Spring 上下文的验证有限:某些全局配置(如安全过滤器链、自定义拦截器)的行为可能与完整应用环境存在差异。

何时使用 @WebMvcTest?

  • 当你需要快速、专注地测试控制器逻辑时。
  • 当你的重点是验证 HTTP 响应格式、状态码和异常处理机制时。
  • 当你不需要启动完整的 Spring 上下文(如数据库、消息队列、完整安全配置)时。

2.2 @SpringBootTest + MockMvc 进行全面集成测试

目的

这种方法可测试整个应用堆栈,包括:

  • ✅ 控制器(Controllers):HTTP 请求的接收、路由、参数绑定与响应处理。
  • ✅ 服务层(Services):核心业务逻辑的正确执行。
  • ✅ 数据访问层(Repositories):与数据库的实际交互,包括 CRUD 操作和事务管理。
  • ✅ 配置(Configuration):安全策略(如 Spring Security)、过滤器、拦截器、消息转换器、序列化设置等全局配置是否按预期生效。

与 @WebMvcTest 不同,此方法不会模拟服务或数据访问层(除非显式指定)。相反,它使用:

  • 一个真实的(或内存中的)数据库(例如,测试时使用 H2)
  • 实际的服务层(除非需要,否则不进行模拟)
  • 完整的 Spring 上下文(与生产环境类似)
核心注解 & 类
  • @SpringBootTest:启动完整的 Spring 应用程序上下文。
  • @AutoConfigureMockMvc: 启用 MockMvc 以进行 HTTP 测试(无需启动真实服务器)。

测试用例:

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest2 {
  // 用于模拟 HTTP 请求。
  @Resource
  private MockMvc mockMvc;
  // 被模拟的服务(真实的服务被忽略)。
  @MockitoBean
  private UserService userService;
  @Test
  public void testSave() throws Exception {
    String jsonBody = """
        {"id": 2, "name": "admin", "age": 33}
        """;
    mockMvc.perform(post("/users")
        .contentType(MediaType.APPLICATION_JSON)
        .content(jsonBody))
        // 检查状态码是否201
        .andExpect(status().isCreated())
        // 检查返回值的name属性是否是admin
        .andExpect(jsonPath("$.name").value("admin"));
  }
}

✅ 优点

  • ✔ 测试完整流程(控制器 → 服务 → 数据访问层 → 数据库)
  • ✔ 发现集成问题(如 JSON 解析错误、数据库约束冲突)
  • ✔ 更贴近真实运行行为(相比模拟测试)

⚠ 缺点

  • ❌ 速度较慢(需启动完整 Spring 上下文和数据库)
  • ❌ 需要配置测试数据库(如 H2、Testcontainers 等)
  • ❌ 难以定位问题(失败可能来自任意一层)

何时使用 @SpringBootTest?

  • 当你需要进行端到端测试(API 到数据库)时。
  • 当模拟不足以满足需求时(例如测试事务、安全配置)。
  • 当需要验证真实数据库约束(如唯一字段、外键关系)时。

2.3 使用 WebTestClient 进行测试

WebTestClient 是 MockMvc 和 TestRestTemplate 的现代、灵活替代方案,支持:

  • ✅ 响应式应用(Spring WebFlux)
  • ✅ 传统的阻塞式控制器(Spring MVC)
  • ✅ 流畅的链式 API,便于进行请求/响应验证

它可以测试:

  • HTTP 端点(REST、GraphQL 等)
  • 响应状态码、响应头和响应体
  • 错误处理和流式响应

测试用例:

// 使用下面2个注解都可以
// @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@WebFluxTest(UserController.class) 
public class UserControllerTest3 {
  @Resource
  private WebTestClient webTestClient;
  @MockitoBean
  private UserService userService;
  @Test
  public void testQueryUsers() throws Exception {
    when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))) ;
    webTestClient.get().uri("/users")  
      .exchange() 
      .expectStatus().isOk()  
      .expectBody()  
      .jsonPath("$[0].name").isEqualTo("pack");
  }
}

✅ 优点

  • ✔ 统一支持 MVC 与 WebFlux 应用
  • ✔ 比 MockMvc 更易读(流畅式 API 风格)
  • ✔ 支持流式响应测试(如 SSE、WebSocket)
  • ✔ 既可用于模拟环境,也可连接真实服务器

⚠ 缺点

  • ❌ 相对较新,部分团队仍偏好 MockMvc
  • ❌ 对于纯 MVC 应用,相比 MockMvc 略缺乏细粒度控制

何时使用 WebTestClient?

  • 若你的应用基于响应式(WebFlux)→ 首选方案。
  • 若你希望使用统一工具测试 MVC 和 WebFlux 控制器。
  • 若你偏好流畅、现代的断言风格,而非 MockMvc 的 DSL 风格。

2.4 使用 TestRestTemplate 进行测试

目的

TestRestTemplate 是一个真实的 HTTP 客户端,向正在运行的 Spring Boot 应用程序发送实际的 HTTP 请求。与模拟请求的 MockMvc 不同,它:

  • ✅ 发起真实的网络调用(如同浏览器或 Postman)
  • ✅ 测试完整的服务器行为(包括过滤器、安全机制和错误处理)
  • ✅ 可用于测试任何 REST 端点(不仅限于 Spring 控制器)

最适合用于:

  • 端到端的 API 测试
  • 认证功能测试(如 OAuth、JWT)
  • 验证负载均衡器与代理行为

测试用例:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTest4 {
  @Resource
  private TestRestTemplate restTemplate;
  @Test
  public void testQueryUsers() throws Exception {
    ResponseEntity<User[]> response = restTemplate.getForEntity("/users", User[].class);  
    assertEquals(HttpStatus.OK, response.getStatusCode());  
    assertEquals("pack", response.getBody()[0].name());  
  }
}

✅ 优点

  • ✔ 测试真实的 HTTP 行为(请求头、Cookie、SSL 等)
  • ✔ 可用于测试外部 API(不仅限于 Spring 控制器)
  • ✔ 适合与其他服务进行集成测试

⚠ 缺点

  • ❌ 速度较慢(需启动完整服务器并进行网络调用)
  • ❌ 相比 MockMvc 控制力较弱(无法直接模拟内部行为)
  • ❌ 更难调试(失败可能源于网络问题而非应用逻辑)

何时使用 TestRestTemplate?

  • 测试 API 网关或代理
  • 验证 HTTPS、CORS 或安全过滤器的行为
  • 需要与外部服务交互的端到端测试

2.5 独立模式 MockMvc(无 Spring 上下文)

这种方式允许你在完全隔离的环境下测试单个控制器,无需:

  • 加载 Spring 上下文
  • 执行自动配置
  • 触发过滤器、拦截器或 AOP 通知

取而代之的是:

  • ✅ 手动创建控制器(并注入模拟的依赖)
  • ✅ 使用 MockMvcBuilders.standaloneSetup()(无需 @SpringBootTest)
  • ✅ 获得极快的测试执行速度(非常适合 TDD 快速反馈)

测试用例:

public class UserControllerTest5 {
  private MockMvc mockMvc;
  private UserService userService = mock(UserService.class);
  @BeforeEach
  public void setup() {
    // 1.手动创建Controller接口
    UserController controller = new UserController(userService);
    // 2.不使用Spring构建MockMvc
    mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
  }
  @Test
  public void testQueryUsers() throws Exception {
    // 3.模拟测试行为
    when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33))); 
    // 4.测试接口  
    mockMvc.perform(get("/users"))  
           .andExpect(status().isOk())  
           .andExpect(jsonPath("$[0].name").value("pack1"));  
  }
}

✅ 优点

  • ✔ 极快执行(无 Spring 启动开销)
  • ✔ 完全掌控依赖(无隐式 @Autowired 行为)
  • ✔ 非常适合单元测试纯控制器逻辑

⚠ 缺点

  • ❌ 不支持 Spring 功能(如 @Valid 数据验证、@ControllerAdvice 异常处理、安全注解等)
  • ❌ 需手动配置(必须显式注入所有依赖)
  • ❌ 不够真实(行为可能与生产环境存在差异)

何时使用 Standalone MockMvc?

  • 需要完全隔离地测试控制器内部逻辑时
  • 进行超高速单元测试,追求极致的 TDD 快速反馈
  • 希望彻底避免 Spring 上下文启动开销的场景

2.6 REST Assured — 流畅 API 测试

目的

REST Assured 是一个用于测试 REST API 的 Java 领域特定语言(DSL),采用流畅的、行为驱动(BDD)风格编写测试。它:

  • ✅ 使测试代码更具可读性(类似自然语言)
  • ✅ 支持复杂验证(JSON Path、XML、Schema 等)
  • ✅ 可与任何 HTTP 服务器(Spring Boot、Node.js 等)配合使用

最适合用于:

  • API 契约测试:验证接口的请求/响应格式是否符合约定。
  • 与外部服务的集成测试:测试应用与第三方 API 的交互。
  • 采用 BDD(Given-When-Then)实践的团队:统一测试语言,提升协作效率。

测试用例:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTest6 {
  // 注入随机端口
  @LocalServerPort
  private int port;
  @Test
  public void testQueryUsers() {
    given().port(port) // 设置端口
        // 当:调用 GET /users
        .when().get("/users")
        // 那么:验证响应
        .then().statusCode(200) // HTTP 200 OK
        .body("[0].name", equalTo("pack")) // 检查 JSON
        .body("size()", greaterThan(1)); // 其他断言
  }
}

注意,你需要引入如下依赖:

<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>rest-assured</artifactId>
  <scope>test</scope>
</dependency>

✅ 优点

  • ✔ 语法可读性强,采用 BDD 风格(清晰的 given-when-then 结构)
  • ✔ 断言功能强大(支持 JSON Path、Hamcrest 匹配器)
  • ✔ 支持多种认证方式(OAuth2、Basic Auth 等
  • ✔ 可用于测试任何 REST API(不仅限于 Spring Boot 应用)

⚠ 缺点

  • ❌ 需要引入额外依赖(增加 pom.xml 或 build.gradle 的体积)
  • ❌ 相比 MockMvc 稍慢(通常基于真实 HTTP 调用)
  • ❌ 学习曲线较陡(DSL 语法有其独特性)

何时使用 REST Assured?

  • 测试第三方或外部 API
  • 编写易于理解、可读性高的集成测试
  • 需要验证复杂的 JSON/XML 响应结构时

2.7 测试 Controller Advice 与异常处理

目的

验证以下关键环节:

  • ✅ 全局异常处理器(@ControllerAdvice)能否正确捕获并处理异常
  • ✅ 自定义错误响应(JSON/XML 格式)是否符合 API 规范
  • ✅ 返回的 HTTP 状态码是否与错误类型匹配(如 404、400、500 等)

首先,准备@ControllerAdvice全局异常处理

@RestControllerAdvice
public class UserControllerAdvice {
  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<?> error(UserNotFoundException e) {
    return ResponseEntity.status(404).body(Map.of("code", -1, "error", e.getMessage())) ;
  }
}
public class UserNotFoundException extends RuntimeException {
  public UserNotFoundException() {
    super();
  }
  public UserNotFoundException(String message) {
    super(message);
  }
  public UserNotFoundException(String message, Throwable cause) {
    super(message, cause);
  }
}

测试用例:

@WebMvcTest(UserController.class)
public class UserControllerTest7 {
  // 用于模拟 HTTP 请求。
  @Resource
  private MockMvc mockMvc;
  // 被模拟的服务
  @MockitoBean
  private UserService userService;
  @Test
  public void testUser() throws Exception {
    when(userService.queryUser(1L)).thenThrow(new UserNotFoundException("用户不存在1"));
    mockMvc.perform(get("/users/1"))  
      .andExpect(status().isNotFound())
      .andExpect(jsonPath("$.error").value("用户不存在"));
  }
}

最佳实践

  • ✔ 结合测试模拟与真实异常场景:既测试手动抛异常的路径,也测试由框架触发的异常(如参数校验失败)
  • ✔ 验证错误响应结构:确保返回的错误 JSON/XML 字段(如 code, message, timestamp)符合 API 文档规范
  • ✔ 在测试中包含错误日志输出:使用 .andDo(print()) 打印请求/响应详情,便于调试失败用例

2.8 使用 Mockito 进行纯单元测试

目的

该方法完全绕过 HTTP 协议和 Spring 框架,仅专注于测试 Java 方法调用。适用于:

  • ✅ 隔离控制器逻辑(例如,调用服务前的请求参数处理与业务判断)
  • ✅ 极致快速的单元测试(无 Spring 上下文启动、无 HTTP 开销)
  • ✅ 验证与依赖组件的交互(如是否正确调用 UserService 及参数传递)

测试用例:

public class UserControllerTest8 {
  private UserService userService = mock(UserService.class);  
  private UserController userController = new UserController(userService);
  @Test
  public void testQueryUsers() throws Exception {
    when(userService.queryUsers()).thenReturn(List.of(new User(1L, "pack", 33)));


    List<User> users = userController.queryUsers() ;  
    assertEquals("pack", users.get(0).name());  
    verify(userService).queryUsers() ; 
  }
}

✅ 优点

  • ✔ 速度最快(无任何框架开销)
  • ✔ 对依赖完全可控(所有依赖均可模拟)
  • ✔ 适合测试控制器内的复杂业务逻辑

⚠ 缺点

  • ❌ 不测试 HTTP 映射(如 @GetMapping、@PostMapping 是否正确)
  • ❌ 忽略序列化/验证逻辑(无 JSON 转换、无参数绑定过程)
  • ❌ 无法覆盖 Spring 特性(如 @Valid 校验、安全注解等)

何时使用纯 Mockito 测试?

  • 测试控制器中的辅助方法或私有逻辑
  • 验证控制器内复杂的条件判断或数据处理流程
  • AI大模型学习福利

    作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

    一、全套AGI大模型学习路线

    AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

    二、640套AI大模型报告合集

    这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    三、AI大模型经典PDF籍

    随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    四、AI大模型商业化落地方案

    因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

    作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

本次测试采取负载测试、并发测试、可靠性测试测试方案采取模拟真实用户使用场景,模拟指定人数在一定时间点击界面产生的请求数。 在并发10(单位个/s)、20、40、80、160、500、1000、2000的基准下,调整用户数(虚拟用户用一个线程,下统称线程数)、点击准备时间(用户点击时间模拟时间,下称Ramp-up单位秒)和用户点击次数(下称循环),例如10个用户,每个用户每5秒点击1次,则线程数为10,Ramp-up为5,循环数为1。详细测试策略请看2.1。 对登录、数据新增(用户)、编辑(用户)、获取(用户)和删除(用户)进行负载测试,获得其稳定负载值。 对全站使用策略100-100-1-1进行并发测试,挑选用户服务所有接口。基础数据服务中挑选和用户服务关联的功能接口5个,组织结构接口4个,和用户服务无关的行政区3个接口。具体接口请查看附件1。 对全站进行可靠性测试,根据以上测试接口,选择稳定的并发数后持续测试-模拟时长8+小时。 稳定性测试是通过运行状态和资源指标的2个方面来分析及评估系统的稳定性,请求记录项响应的时间平均值、最小值、最大值、标准偏差、异常(百分比)、吞吐量、接收、发送、平均字节数,服务器资源指标CPU、Memory,在此额外添加记录数据库数据。通过调试测试策略、分析实验数据得出相关系统稳定性的结论,从而达到平台能力验证、规划能力、性能调优、缺陷发现等目的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值