进行系统化的 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大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获
作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量
4万+

被折叠的 条评论
为什么被折叠?



