当前位置: 首页 > news >正文

如何编写Controller的测试代码?

在日常工作中,看过很多开发人员不写代码测试,大部分理由是“太忙“或者”没必要”,更严重的是很多开发人员甚至不知道如何写测试代码,本文我们总结了一位腾讯后端对 Controller层代码优秀的测试经验,希望对你有帮助。

在 如何编写优雅的 Controller代码? 这篇文章中,我们分析了 Controller层的 6个主要职责,本文将分析如何对 Controller层进行 360度无死角测试,首先回顾下 Controller层的6个主要职责:

  1. 接收 HTTP(s)请求
  2. 解析请求参数
  3. 校验请求参数
  4. 调用业务方法
  5. 组织返回数据
  6. 统一异常处理

理解了 Controller的职责,我们才能更有目的对它进行测试,如下代码,包含了 Controller的 6个主要职责,我们该如何编写测试代码?

@RestController public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/user/register") public String getGradeById(@Validated @RequestBody User user) { // 调用注册的业务方法 String userId = userService.register(user); return userId; } } public class User { @NotBlank(message = "Nickname is required.") private String nickname; private Integer age; // getters and setters and constructors }

为什么要测试?

作为一名程序员,代码质量是我们必须守住的底线,对于自己编写的代码一定需要进过测试,而不是把测试工作好不负责的全部交给测试人员,这样才能保证代码的可靠性。

对于开发人员,保证代码质量最有效的方式是测试,最常见的测试方式有 2种:单元测试(Unit Testing)和 集成测试(Integration Testing)。

单元测试的目的是验证单个功能单元的行为是否正确,这里的功能单元通常是一个类或方法,单元测试最大的优点是可以在不依赖于其他部分的前提下测试某个功能单元。

集成测试的目的是验证多个功能单元之间的交互是否正确,可能涉及数据库、文件系统、网络等外部系统,如果需要排除外部系统的干扰,可以对他们进行 mock。

如何测试?

对于 Controller层的代码测试,因为单元测试的局限性,所以我们需要采用集成测试,本文将使用SpringBootTestMockito两个主流的测试框架进行分析,下面先说明几个重要的组件:

@WebMvcTest

@WebMvcTest是 Spring Boot提供的一个注解,用于测试 SpringMVC的 Controller,它能够启动一个最小化的 Spring应用上下文,包含仅与 Web层相关的 bean,从而快速且高效地测试 Controller的功能。

@WebMvcTest主要功能如下:

  • 加载 Web层相关的 Bean: @WebMvcTest只加载与 Web层相关的 bean,例如 Controller、过滤器、拦截器等,这使得测试环境更加轻量级。
  • 自动配置 MockMvc: @WebMvcTest 自动配置 MockMvc,使得测试控制器的 HTTP请求处理变得简单。
  • 支持 @MockBean注解: 可以使用 @MockBean注解来模拟服务层的 bean,以隔离控制器测试,不依赖实际的服务层实现。

MockMvc:

MockMvc是 Spring框架中用于测试 Spring MVC控制器的主要工具,它允许开发者在不启动整个 HTTP服务器的情况下,测试控制器的请求处理方法。MockMvc 提供了一种方式来模拟 HTTP请求并验证响应,确保控制器行为符合预期。

MockMvc的主要功能如下:

  • 模拟 HTTP 请求: MockMvc 可以模拟各种 HTTP 请求,如 GET、POST、PUT、DELETE 等。
  • 验证响应: MockMvc 提供了一组丰富的断言来验证响应的状态码、内容类型、内容、头信息等。
  • 测试控制器逻辑: 通过模拟请求并验证响应,可以测试控制器的业务逻辑、路径变量、请求参数、请求体等。
  • 集成测试: 可以与 Spring 的其他测试工具结合,进行更复杂的集成测试。

Mockito

Mockito 是一个流行的 Java测试框架,用于创建和配置 mock 对象。它主要用于单元测试中,通过模拟依赖对象的行为,使得测试目标对象能够独立于其依赖项进行测试。

Mockito主要功能如下:

  • 创建 Mock 对象: 使用 @Mock注解或 Mockito.mock()方法创建 mock对象。
  • 定义行为: 使用 when 和 thenReturn 等方法定义 mock 对象的方法行为。
  • 验证行为: 使用 verify 方法验证 mock 对象的方法是否被调用,以及调用的次数和参数。
  • 参数匹配: 使用 any、eq 等参数匹配器来验证方法调用时传入的参数。
  • 模拟异常: 使用 thenThrow 方法模拟方法抛出异常的行为。

ObjectMapper

它是 Jackson库中的核心类之一,用于将 Java 对象转换为 JSON 字符串,或者将 JSON 字符串转换为 Java 对象。在 Spring 应用中,ObjectMapper 被广泛用于处理 JSON 数据。

ObjectMapper的主要功能如下:

  • 序列化(Serialization): 将 Java 对象转换为 JSON 字符串。
  • 反序列化(Deserialization): 将 JSON 字符串转换为 Java 对象。
  • 读取和写入 JSON 文件或流: 直接从文件、输入流读取 JSON 数据,或将 JSON 数据写入文件、输出流。

所以,一个包含上述所有组件的完整代码示例如下:

@WebMvcTest(controllers = RegisterRestController.class) class RegisterRestControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private XXX xxx; // 用户业务的bean @Test void test() throws Exception { mockMvc.perform(...); } }

介绍了上面几个集成测试的组件之后,接下来我们就可以详解的分析他们是如何应用到 Controller的集成测试中。

测试详解

我们按照 Controller的 6个主要职责来分别讲解他们是如何进行测试的。

测试接收 HTTP(s)请求

Controller是接收请求的入口,因此对于 Controller接收 HTTP(s)请求验证 Controller是否侦听某个 HTTP 请求非常简单,我们只需调用MockMvc.perform()方法,返回 200代表成功,返回非200就代表异常,示例代码如下:

@Test void whenReceiveHttpRqe_thenReturns200() throws Exception { mockMvc.perform(post("/user/register") .contentType("application/json")) .andExpect(status().isOk()); }

测试解析请求参数

Controller是通过@PathVariable@RequestBody@RequestParam3种方式来接收参数的,因此,下面的示例代码分别模拟这 3种方式是如何进行参数传递的。

@Autowired private ObjectMapper objectMapper; @Test void whenValidInput_thenReturns200() throws Exception { User user = new User("zhangsan", 21); mockMvc.perform(post("/user/register/{id}", 1111) // 模拟通过 @PathVariable传递参数 .contentType("application/json") .param("name", "张三") // 模拟通过 @RequestParam传递参数 .content(objectMapper.writeValueAsString(user))) // 模拟通过 @RequestBody传递参数 .andExpect(status().isOk()); }

通过上面的方式,我们模拟了一个正常的 HTTP请求,并且使用 3种方式进行参数传递。

测试校验请求参数

测试参数的校验,主要是为了检查代码逻辑有没有对参数进行有效的验证,比如,必填字段判空,字符串最大长度限制,数字最大值和最小值校验,手机号或邮箱格式校验等。

参数校验测试一般分正常测试和按预期失败的异常测试,特别需要边界的测试。如下示例,给出了测试通过和符合预期并返回 400的 BadRequest:

public class User { @NotBlank(message = "Nickname is required.") private String nickname; private Integer age; // getters and setters and constructors } @Test void whenNicknameNull_thenReturns400() throws Exception { User user = new User(null, 21); // 当 nickname为空时,会抛出 400的 BadRequest异常 mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isOk()); } @Test void testReturns200() throws Exception { User user = new User("zhangsan", 21); mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isBadRequest()); }

测试调用业务方法

对于业务方法调用的测试,通常我们会使用Mockito.mock来模拟业务方法的返回值,而业务方式的真实运行逻辑会在 Server的单元测试中完成,如下示例代码:

// 调用注册的业务方法 String userId = userService.register(user); @Test void whenLogic_thenReturnsExceptData() throws Exception { User user = new User(null, 21); // mock userService.register(user)返回值为 userId Mockito.when(userService.register(user)).thenReturn("userId"); mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isOk()) .andExpect(jsonPath("$.data", is("userId"))); }

测试组织返回数据

在业务代码执行完之后,我们需要对 HTTP对应相应,因此可以使用 andReturn()方法将 HTTP交互的结果存储在 MvcResult类型的变量中,然后从响应正文中读取 JSON字符串,并使用 isEqualToIgnoringWhitespace()将其与预期字符串进行比较,如下示例:

// 调用注册的业务方法 String userId = userService.register(user); @Test void whenLogic_thenReturnsExceptData() throws Exception { User user = new User("zhangsan", 21); // mock userService.register(user)返回值为 userId Mockito.when(userService.register(user)).thenReturn("userId"); MvcResult mvcResult = mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andReturn(); //将结果返回 String expectedResponse = "userId"; String actualResponseBody = mvcResult.getResponse().getContentAsString(); assertThat(actualResponseBody).isEqualToIgnoringWhitespace( objectMapper.writeValueAsString(expectedResponse)); }

测试统一异常处理

测试异是指如果发生异常,Controller应返回特定的 HTTP状态,比如 200,400,500等等, 默认情况下,Spring 会处理其中的大多数情况。但是,如果我们有一个自定义异常处理,我们想要测试它。假设我们要返回一个结构化的 JSON 错误响应,其中包含请求中每个无效字段的字段名称和错误消息。我们会创建一个这样的@ControllerAdvice:

@Test void whenNullValue_thenReturns400AndErrorResult() throws Exception { User user = new User("zhangsan", 21); MvcResult mvcResult = mockMvc.perform(post("/user/register") .contentType("application/json") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isBadRequest()) .andReturn(); ErrorResult expectedErrorResponse = new ErrorResult("Nickname", "Nickname is required."); String actualResponse = mvcResult.getResponse().getContentAsString(); String expectedResponseBody = objectMapper.writeValueAsString(expectedErrorResponse); assertThat(actualResponse).isEqualToIgnoringWhitespace(expectedErrorResponse); }

总结

本文结合SpringBootTestMockito两个主流的测试框架,对 Controller各个职责进行全面的测试,并且给出了比较详解的示例代码,通过本文的分析,我们不仅可以学会对 Controller的测试,同时我们还应该触类旁通,将里面优秀的思维应用到其他层级代码的测试。

代码质量是开发人员必须守住的底线,所以在日常的开发中一定要秉着对自己负责的态度,对自己的代码进行测试。

对于开发人员,使用最多的测试方式是单元测试和集成测试。

最后:下方这份完整的软件测试 视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​​件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

http://www.cnnetsun.cn/news/1954635.html

相关文章:

  • 《RocketMQ实战与进阶》 04 消息发送常见错误与解决方案
  • 苏州2026年,探秘苏州灌装机工厂的智造新篇章
  • 富集分析结果太杂乱?3个ggplot2技巧让你的气泡图秒变高颜值SCI配图
  • 构建智能能源管理系统的7个关键技术突破:OpenEMS实战指南
  • 1Panel+Docker私有仓库实战:解决镜像拉取慢/失败问题(含WordPress案例)
  • 用周立功CAN分析仪抓包解析电动汽车充电握手报文(BMS/充电机通信实战)
  • YzmCMS 5.3.0 ‘采集节点’功能竟是SSRF后门?一次对功能滥用的深度安全审计
  • BetterNCM-Installer:一键解锁网易云音乐PC版的终极插件管理器
  • Python自动化操作Creo的5个实用技巧(附代码示例)
  • GSV9001E@ACP# 参数规格 + 产品特色总结分享
  • 从.mat到.txt:手把手教你用Matlab load函数搞定各种格式的数据导入
  • 企业如何用SaaS平台实现数字化转型?3步搭建高效管理体系的实战指南
  • 别再只把Logstash当日志收集器了!手把手教你用它搞定MySQL到Elasticsearch的数据同步
  • STM32学习——.map文件知识的学习
  • 宝塔面板SSH提示连接被拒绝_检查服务器端口开关
  • 别再只盯着评分了!用BPR算法处理隐式反馈数据,让你的推荐系统更懂用户
  • 幻境·流金入门必看:DiffSynth-Studio+玄金美学环境搭建详解
  • Python函数实战:从基础分段到高级参数处理
  • 揭秘煤炉Mercari防封号终极指南
  • 从扫地机器人到无人机:用Python模拟Bug1/Bug2算法,看经典避障如何影响现代机器人
  • 【FPGA】Vivado综合进程异常终止(PID Not Specified)排查与修复指南
  • 亮数据 Bright Data 第二季度直播倒计时!
  • 别再只改单元格了!PyQt5 QTableWidget表头(horizontalHeader/verticalHeader)的5个实用技巧与避坑指南
  • 别再让虚拟机卡顿了!保姆级详解VMware ESXi内存回收三剑客:TPS、Ballooning与Swapping
  • 新概念英语(第三册)精读与场景应用——Lesson 6 至 Lesson 10 核心主题解析
  • 请在vscode中使用opencode
  • 知识博主如何用直播筛选高意向客户
  • 生物信息学必备:用R语言密度图揪出测序数据中的异常分布(含带宽调整技巧)
  • 配置爆炸危机预警!SITS2026最新数据:单系统平均配置项达2143+,AI生成方案已成P0级技术刚需——立即获取首批200个预训练领域模型访问权限
  • Avalonia开发环境搭建全攻略:从Visual Studio到Rider的保姆级教程