楼层: 首页/ 软件技术/ Spring Boot 3.5/ 测试:切片测试与 MockMvc
9

测试:切片测试与 MockMvc

Testing

Spring Boot 测试最大的好处是切片测试:测 Controller 就只起 Web 层,连数据库都不碰;测 Repository 就只起 JPA 层。比 @SpringBootTest 全量启动快得多。

注解测什么
@SpringBootTest全量启动容器,集成测试用。
@WebMvcTest只起 Spring MVC 层,测 Controller。配合 MockMvc 发请求。
@DataJpaTest只起 JPA 层,测 Repository,自动用内存数据库。
@MockBean把容器里某个 Bean 替换成 Mockito Mock,不真正调它。

用 MockMvc 测一个 Controller 接口

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(HelloController.class) // 只起 HelloController 这一个 Controller class HelloControllerTest { @Autowired MockMvc mockMvc; // 模拟 HTTP 请求,不用真起 Tomcat @Test void helloShouldReturnGreeting() throws Exception { mockMvc.perform(get("/hello").param("name", "豆包")) .andExpect(status().isOk()) .andExpect(content().string(containsString("豆包"))); } }

用 @MockBean 替换真实依赖

@WebMvcTest(TaskController.class) class TaskControllerTest { @Autowired MockMvc mockMvc; // TaskService 是真实接口,但这里用 Mockito 假的替换掉 @MockBean TaskService taskService; @Test void listShouldReturnTasks() throws Exception { // 打桩:调用 service.list(0, 10) 时返回固定数据 when(taskService.list(0, 10)).thenReturn(...); mockMvc.perform(get("/api/tasks")) .andExpect(status().isOk()); } }

如果测试需要真数据库,可以用 Testcontainers——它在测试时自动拉起一个 Docker 容器跑 PostgreSQL/Redis,测完自动销毁。生产用什么数据库,测试就用什么,避免"本地 H2 跑得好好的,上 PostgreSQL 全是坑"。

@DataJpaTest:只起数据库层测 Repository

测 Repository 不用起整个 Web 容器。@DataJpaTest 只加载 JPA 相关 Bean,自动配一个内存数据库(H2),每个测试方法跑完自动回滚。

import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.beans.factory.annotation.Autowired; @DataJpaTest // 只起 JPA 层,不起 Web 层,秒级启动 class TaskRepositoryTest { @Autowired TaskRepository repo; @Autowired TestEntityManager em; // 测试专用的实体管理器 @Test void findByDoneShouldFilter() { em.persist(new Task(1, "买菜", false)); em.persist(new Task(2, "做饭", true)); List<Task> open = repo.findByDone(false); assertEquals(1, open.size()); assertEquals("买菜", open.get(0).getTitle()); // 方法结束自动回滚,不污染下一个测试 } }

@SpringBootTest:全量集成测试(慢但真实)

@SpringBootTest // 起完整容器,接近真实运行环境 class TaskServiceIntegrationTest { @Autowired TaskService taskService; @Test void createShouldSaveToDb() { Task t = taskService.create("集成测试任务"); assertNotNull(t.getId()); // 真的落库了 assertEquals("集成测试任务", t.getTitle()); } }
三种测试的启动速度对比: @DataJpaTest → 秒级(只起 JPA) @WebMvcTest → 秒级(只起 MVC) @SpringBootTest → 十秒级(全量起,含数据库、Redis...) # 写单元/切片测试用前两个,写少数关键链路的端到端测试才用 @SpringBootTest。

Testcontainers:用真实数据库跑测试,不骗自己

@DataJpaTest 默认用 H2 内存库,但 H2 和你生产的 MySQL 语法、索引行为不完全一样——"测试通过、上线炸"。Testcontainers 在测试时自动起一个一次性的 MySQL 容器,测完就删,让测试用的库和生产一模一样。

@SpringBootTest class UserRepositoryIT { // 测试前自动起一个 MySQL 8 容器,测完自动销毁 @Container static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0"); @DynamicPropertySource static void props(DynamicPropertyRegistry r) { r.add("spring.datasource.url", mysql::getJdbcUrl); r.add("spring.datasource.username", mysql::getUsername); r.add("spring.datasource.password", mysql::getPassword); } // 之后用 @Autowired 注入 Repository,跑的就是真 MySQL }

论什么时候值得上

测试启动要拉镜像、起容器,比 H2 慢几十秒。所以单元测试别用,留给少量关键的集成测试(IT)——尤其是写了复杂 SQL、依赖数据库方言的地方。CI 环境装了 Docker 就能跑。

本章面试题 · 第 9 章(测试)

1.(对比题)@SpringBootTest、@WebMvcTest、@DataJpaTest 分别什么时候用?

查看答案

答案:@WebMvcTest 只测 Controller(Service 用 @MockBean 替身);@DataJpaTest 只测 Repository(自动用内存库);@SpringBootTest 起全容器,测跨层集成。解析:原则:能切片不全量,切片快且隔离。

2.(理解题)@MockBean 和 @SpyBean 的区别?

查看答案

答案:@MockBean 整个 Bean 换成假的,方法调用要你 stub;@SpyBean 包装真实 Bean,默认调真方法,只拦截你指定的几个方法。解析:WebMvcTest 里替换 Service 用 @MockBean;想保留真实逻辑只改一处用 @SpyBean。