Spring Boot 单元测试详解:从基础配置到 Web 安全测试实战 单元测试是保障代码质量的关键环节,Spring Boot 基于 Spring 单元测试框架提供了更简洁的测试支持,涵盖普通 Bean 测试、Web 接口测试、安全权限测试等场景。从 “Spring 传统测试 vs Spring Boot 测试→基础测试配置→Web 接口测试(MockMvc)→安全测试(Spring Security) ” 四个维度,系统讲解 Spring Boot 单元测试的实现方法与最佳实践。
Spring 传统测试与 Spring Boot 测试的核心差异 在 Spring Boot 出现前,传统 Spring 项目的单元测试需要手动配置上下文、指定配置文件,步骤繁琐;而 Spring Boot 通过 @SpringBootTest 注解简化了配置,实现 “零 XML 配置” 的测试环境搭建。
对比维度
传统 Spring 测试
Spring Boot 测试
核心注解
@RunWith(SpringJUnit4ClassRunner.class) + @ContextConfiguration
@RunWith(SpringRunner.class) + @SpringBootTest
配置文件指定
需手动通过 locations 指定 XML/Java 配置(如 @ContextConfiguration(locations = "classpath:springmvc.xml"))
自动扫描主程序类(@SpringBootApplication 标注类)的配置,无需手动指定
Web 环境支持
需添加 @WebAppConfiguration 并手动初始化 MockMvc
通过 @SpringBootTest(webEnvironment = ...) 快速指定 Web 环境类型(Mock / 真实容器)
依赖简化
需手动引入 spring-test、junit 等依赖
引入 spring-boot-starter-test 一站式依赖,包含所有测试组件
Spring Boot 基础单元测试:环境搭建与普通 Bean 测试 Spring Boot 基础测试主要用于验证普通 Bean(如配置类、Service 类)的逻辑正确性,核心是通过 @SpringBootTest 自动加载 Spring 上下文,注入待测试 Bean。
引入测试依赖 Spring Boot 提供 spring-boot-starter-test 依赖,包含 JUnit、Spring Test、MockMvc 等核心测试组件,无需单独引入其他依赖:
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
编写基础测试类 场景:测试自定义配置类 CustomConfig 的属性注入是否正确 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Data @Component @PropertySource(value = "classpath:custom.yml", factory = YmlPropertyFactory.class) @ConfigurationProperties(prefix = "custom") public class CustomConfig { private String name; private Map<String, String> typeFields; } 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 static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest public class CustomConfigTest { @Autowired private CustomConfig customConfig; @Test public void testConfigInjection () { assertNotNull("配置 name 未注入" , customConfig.getName()); assertEquals("配置 name 注入错误" , "订单服务" , customConfig.getName()); assertNotNull("配置 typeFields 未注入" , customConfig.getTypeFields()); assertTrue("typeFields 应包含 key1" , customConfig.getTypeFields().containsKey("key1" )); assertEquals("key1 的值错误" , "value1" , customConfig.getTypeFields().get("key1" )); } }
3. @SpringBootTest 核心属性:webEnvironment @SpringBootTest 的 webEnvironment 属性用于指定 Web 环境类型,默认值为 WebEnvironment.MOCK,适用于不同测试场景:
WebEnvironment 类型
核心特点
适用场景
MOCK (默认)
加载 Web 上下文,提供 Mock Servlet 环境,不启动内嵌容器
测试 Controller 接口(无需真实端口,通过 MockMvc 模拟请求)
RANDOM_PORT
启动内嵌容器(如 Tomcat),监听随机端口
测试真实 HTTP 请求(如跨服务调用、外部接口依赖)
DEFINED_PORT
启动内嵌容器,监听配置文件中指定的端口(server.port)
测试固定端口的场景(如与外部系统约定端口)
NONE
仅加载 Spring 上下文,不提供 Web 环境
测试普通 Bean(如 Service、Repository),无 Web 依赖
Web 接口测试:使用 MockMvc 模拟 HTTP 请求 在 Web 项目中,需测试 Controller 接口的请求处理逻辑(如参数校验、响应状态、返回数据)。Spring Boot 提供 MockMvc 工具,可在不启动真实容器 的情况下模拟 HTTP 请求(GET/POST/PUT/DELETE),高效验证接口正确性。
初始化 MockMvc 通过 @AutoConfigureMockMvc 自动配置 MockMvc,或手动通过 MockMvcBuilders 构建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) @AutoConfigureMockMvc public class UserControllerTest { @Autowired private MockMvc mockMvc; @Test public void testGetUserById () throws Exception { mockMvc.perform( get("/user/{id}" , 1 ) .header("Authorization" , "Bearer token123" ) .param("name" , "张三" ) ) .andExpect(status().isOk()) .andExpect(jsonPath("$.id" ).value(1 )) .andExpect(jsonPath("$.name" ).value("张三" )) .andExpect(jsonPath("$.age" ).value(25 )) .andDo(result -> System.out.println("响应内容:" + result.getResponse().getContentAsString())); } }
MockMvc 核心 API 说明
API 分类
核心方法
作用描述
请求构建
MockMvcRequestBuilders.get/post/put/delete
构建对应 HTTP 方法的请求,支持路径参数、请求头、请求体
响应断言
MockMvcResultMatchers.status()
断言响应状态码(如 isOk()=200、isBadRequest()=400)
MockMvcResultMatchers.jsonPath()
断言 JSON 响应体的字段值(如 $.name 匹配指定值)
MockMvcResultMatchers.view()
断言视图名称(适用于 thymeleaf/jsp 视图)
结果处理
andDo(result -> ...)
处理测试结果(如打印响应内容、日志记录)
andReturn()
获取测试结果对象(如响应体、请求信息)
测试 POST 接口(带请求体) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.alibaba.fastjson.JSON;import org.springframework.http.MediaType;@Test public void testCreateUser () throws Exception { User newUser = new User (null , "李四" , 30 ); mockMvc.perform( post("/user" ) .contentType(MediaType.APPLICATION_JSON) .content(JSON.toJSONString(newUser)) ) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name" ).value("李四" )) .andExpect(jsonPath("$.age" ).value(30 )); }
Spring Security 安全测试:模拟认证用户 若项目集成了 Spring Security(权限控制),普通接口测试会因 “未认证” 被拦截。需通过 spring-security-test 依赖模拟认证用户,验证权限控制逻辑(如角色权限、接口访问控制)。
引入 Spring Security 测试依赖 1 2 3 4 5 <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-test</artifactId > <scope > test</scope > </dependency >
初始化 Security 支持的 MockMvc 通过 SecurityMockMvcConfigurers.springSecurity() 为 MockMvc 添加 Security 支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;import org.springframework.test.web.servlet.setup.MockMvcBuilders;import org.springframework.web.context.WebApplicationContext;@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class SecuredControllerTest { @Autowired private WebApplicationContext webApplicationContext; private MockMvc mockMvc; @org .junit.Before public void setup () { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .apply(SecurityMockMvcConfigurers.springSecurity()) .build(); } }
模拟认证用户(两种方式) 方式 1:@WithMockUser 注解(快速模拟用户) @WithMockUser 注解用于快速创建一个 “虚拟认证用户”,无需真实数据库用户,适用于简单权限测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.springframework.security.test.context.support.WithMockUser;@Test @WithMockUser(username = "root", password = "123456", roles = "ADMIN") public void testAdminDashboardWithMockUser () throws Exception { mockMvc.perform(get("/admin/dashboard" )) .andExpect(status().isOk()) .andExpect(content().string("Admin Dashboard" )); } @Test @WithMockUser(roles = "USER") public void testAdminDashboardWithNoPermission () throws Exception { mockMvc.perform(get("/admin/dashboard" )) .andExpect(status().isForbidden()); }
方式 2:@WithUserDetails 注解(使用真实用户数据) 若需使用数据库中已存在的用户(通过 UserDetailsService 加载),可通过 @WithUserDetails 注解指定用户名,自动从数据库加载用户信息:
1 2 3 4 5 6 7 8 9 10 import org.springframework.security.test.context.support.WithUserDetails;@Test @WithUserDetails("admin") public void testAdminDashboardWithRealUser () throws Exception { mockMvc.perform(get("/admin/dashboard" )) .andExpect(status().isOk()) .andExpect(content().string("Admin Dashboard" )); }
高级测试场景:Service 层与 Repository 层测试 除了 Controller 测试,Service 层(业务逻辑)和 Repository 层(数据访问)也是单元测试的重点,需结合 Mock 工具(如 Mockito)隔离外部依赖。
Service 层测试(使用 Mockito 模拟 Repository) Service 层依赖 Repository 层(如 MyBatis、JPA),测试时需通过 @MockBean 模拟 Repository,避免依赖真实数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.junit.Test;import org.junit.runner.RunWith;import org.mockito.Mockito;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.mock.mockito.MockBean;import org.springframework.test.context.junit4.SpringRunner;import static org.junit.Assert.assertEquals;@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) public class UserServiceTest { @MockBean private UserRepository userRepository; @Autowired private UserService userService; @Test public void testGetUserNameById () { User mockUser = new User (1L , "王五" , 28 ); Mockito.when (userRepository.findById(1L )).thenReturn(java.util.Optional.of(mockUser)); String userName = userService.getUserNameById(1L ); assertEquals("Service 方法返回错误" , "王五" , userName); Mockito.verify(userRepository, Mockito.times(1 )).findById(1L ); } @Test(expected = RuntimeException.class) public void testGetUserNameById_UserNotFound () { Mockito.when (userRepository.findById(99L )).thenReturn(java.util.Optional.empty()); userService.getUserNameById(99L ); } }
Repository 层测试(使用 H2 内存数据库) Repository 层测试需验证数据访问逻辑(如 SQL 语句、查询条件),可使用 H2 内存数据库(无需真实数据库,测试完成后数据自动清空):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;import org.springframework.test.context.junit4.SpringRunner;import static org.junit.Assert.assertTrue;@RunWith(SpringRunner.class) @DataJpaTest public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test public void testFindByName () { User user = new User (null , "赵六" , 35 ); userRepository.save(user); boolean exists = userRepository.existsByName("赵六" ); assertTrue("Repository 查询错误,用户应存在" , exists); } }
H2 数据库配置 (application-test.yml):
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: h2: console: enabled: true jpa: hibernate: ddl-auto: create-drop show-sql: true
单元测试最佳实践
测试隔离 :每个测试方法独立运行,不依赖其他测试的结果(如使用 @Before 初始化数据,@After 清理数据);
重点覆盖 :优先测试核心业务逻辑(如 Service 层的复杂计算、权限控制),而非简单的 Getter/Setter 方法;
避免真实依赖 :Service 测试用 @MockBean 模拟 Repository,Controller 测试用 MockMvc 模拟 HTTP 请求,减少对外部系统(数据库、第三方接口)的依赖;
断言明确 :每个测试方法需包含至少一个断言(如 assertEquals、assertTrue),明确测试目标;
命名规范 :测试方法名应清晰表达测试场景(如 testGetUserById_UserNotFound 表示 “测试用户不存在的场景”)。
总结 Spring Boot 单元测试通过 @SpringBootTest 简化了环境搭建,结合 MockMvc、Mockito、Spring Security Test 等工具,可覆盖从 Controller 到 Repository 的全链路测试:
基础测试 :使用 @SpringBootTest 测试普通 Bean,验证配置注入、业务逻辑;
Web 测试 :使用 MockMvc 模拟 HTTP 请求,测试 Controller 接口的响应状态和数据;
安全测试 :使用 @WithMockUser/@WithUserDetails 模拟认证用户,验证权限控制;
数据层测试 :使用 H2 内存数据库测试 Repository,避免真实数据库依赖