CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件测试入门/课时/Spring Boot 测试
RawBlame
课时目录12 节

软件测试入门

  1. 01测试金字塔
  2. 02JUnit 5 基础
  3. 03Mockito 与 Mock
  4. 04Spring Boot 测试
  5. 05MockMvc 接口测试
  6. 06集成测试
  7. 07TDD 与可维护性
  8. 08前端单元测试
  9. 09E2E 与 Playwright
  10. 10覆盖率与质量门禁
  11. 11CI 中的测试
  12. 12综合练习

Spring Boot 测试

@SpringBootTest、@MockBean、测试切片。

40 分钟

Spring Boot 测试

这一课你将能做到

  1. 区分纯 Mockito 单测、@WebMvcTest 切片、@SpringBootTest 全上下文三档成本,按问题选档位
  2. 用 @MockitoBean 在容器内替换依赖,把 Controller 切片测跑起来
  3. 说清 Spring 上下文缓存为什么让测试「越跑越快」,以及什么会破坏缓存

为什么要学

JUnit + Mockito 再快,也测不到「Bean 有没有装配对、全局异常处理器有没有把 BusinessException 翻成 400、Jackson 序列化出来的 JSON 字段名对不对」。这些恰恰是 CodeForge 这类项目最容易回归的地方:GlobalExceptionHandler 改一行,全站错误响应都可能变形。

Spring 测试模块在 JUnit 之上加载应用上下文:全量或切片。人话:这是给 Spring 装配与 Web 层做的体检——比纯单测贵,比真浏览器便宜得多。

版本注意:Spring Boot 3.4 起旧的 @MockBean 已标记废弃,现行写法是 @MockitoBean(来自 org.springframework.test.context.bean.override.mockito)。网上旧教程写 @MockBean 的,知道是同一意图即可,新代码统一用 @MockitoBean。

先建立图像

纯单元:   JUnit → new Service(@Mock Mapper)        无 Spring,毫秒级
切片:     @WebMvcTest → 只装配 MVC + Jackson 等      启一点 Spring,亚秒级
全上下文: @SpringBootTest → 几乎整站 Bean            最接近生产,秒级起

@Mock        只在测试类里存在的假对象(配 @InjectMocks)
@MockitoBean 放进 Spring 容器,替换同类型 Bean
注解加载范围适合不适合
@ExtendWith(MockitoExtension.class)无容器Service 业务分支装配、序列化
@WebMvcTest(XxxController.class)MVC + Jackson + 异常处理HTTP 映射、校验、JSONMapper/SQL
@SpringBootTest(webEnvironment = NONE)完整上下文多 Bean 协作、装配体检日常改一行跑一次
@WebMvcTest + @AutoConfigureMockMvc全上下文 + MockMvc需要真实过滤器链时追求速度

关键机制:上下文缓存。相同配置(同样注解、同样 profile、同样 @MockitoBean 集合)的测试类共享同一个 Spring 上下文,第二个类起跳几乎零成本。所以「同类测试集中配置」是性能习惯,不是洁癖。

跟着做

步骤 1:给 SubmissionController 写一个 Web 切片测

import cn.dev33.satoken.stp.StpUtil;
import com.codeforge.controller.SubmissionController;
import com.codeforge.entity.Submission;
import com.codeforge.service.SubmissionService;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mockStatic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(SubmissionController.class)
class SubmissionControllerSliceTest {

    @Autowired
    MockMvc mockMvc;

    @MockitoBean
    SubmissionService submissionService;

    @Test
    void 提交判题接口返回记录JSON() throws Exception {
        try (MockedStatic<StpUtil> stp = mockStatic(StpUtil.class)) {
            stp.when(StpUtil::getLoginIdAsLong).thenReturn(3L);

            Submission s = new Submission();
            s.setId(10L);
            s.setUserId(3L);
            s.setStatus("pending");
            when(submissionService.submit(3L, 5L, "java", "class A {}"))
                .thenReturn(s);

            mockMvc.perform(post("/submissions")
                    .contentType("application/json")
                    .content("""
                        {"lessonId":5,"language":"java","code":"class A {}"}
                        """))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(10))
                .andExpect(jsonPath("$.status").value("pending"));
        }
    }
}

应看到:上下文启动远快于全站;MyBatis-Flex、Redis、DataSource 这些 Bean 根本没被加载。Controller 直接调用 StpUtil 静态方法,所以静态替身仍要 mock——切片解决的是「装配」,不是「一切」。

步骤 2:体验「切片缺 Bean」并用 @MockitoBean 逐个补

给 AuthController 写切片,你会立刻撞上缺 Bean:

@WebMvcTest(AuthController.class)
class AuthControllerSliceTest {

    @Autowired MockMvc mockMvc;

    @MockitoBean AuthService authService;
    @MockitoBean CaptchaService captchaService;
    @MockitoBean AuthCookieService authCookieService;
    @MockitoBean MemberService memberService;

    @Test
    void 注册接口返回token() throws Exception {
        when(captchaService.consume(any())).thenReturn(true);
        when(authService.register("neo", "neo@example.com", "123456"))
            .thenReturn("token-1");
        when(authService.getCurrentUser()).thenReturn(new User());

        mockMvc.perform(post("/auth/register")
                .contentType("application/json")
                .content("""
                    {"username":"neo","email":"neo@example.com",
                     "password":"123456","captchaToken":"ok"}
                    """))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.token").value("token-1"));
    }
}

报 NoSuchBeanDefinitionException 不是故障,是特性:切片只装 Web 相关组件,Controller 依赖的每个 Service 都要显式给替身。这逼你想清楚「这个测试到底证明什么」。

步骤 3:对比全上下文,亲眼看缓存

@SpringBootTest
class AppContextSmokeTest {

    @Autowired TrackService trackService;   // 能注入 = 装配成功

    @Test
    void 整站上下文能启动() {
        assertThat(trackService).isNotNull();
    }
}

本地分别跑切片与全上下文,看日志里 Started CodeForgeApplication in X seconds。再把两个 @SpringBootTest 类连跑:第二个类明显快——那就是上下文缓存在生效。

步骤 4:理解 @MockitoBean 的容器语义

同一需求「Service 抛业务异常」的两种写法:

  • 不测 HTTP:@Mock UserMapper + @InjectMocks AuthService(上一课)
  • 要测 HTTP 状态码与 JSON 错误体:@MockitoBean AuthService + MockMvc(本课)

人话:@MockitoBean 是「跟容器说:这个类型改用我的假货」。漏写它,容器会注入真 Service → 真打库 → 测试变慢甚至起不来。

步骤 5:test profile 与外部依赖隔离

建 src/test/resources/application-test.yml(示意):

spring:
  data:
    redis:
      repositories:
        enabled: false
sa-token:
  is-read-cookie: false

原则:切片测试不该连任何外部中间件;需要真 Redis/MySQL 的场景交给第 6 课的 Testcontainers,而不是让单测依赖本机环境。

讲清楚要点

  1. 全上下文贵,能切片就切片:先问「我要证明装配,还是证明 HTTP 契约」,答案决定注解。
  2. 切片缺 Bean 是特性不是 bug:用 @MockitoBean / @Import 补最小集合,而不是一缺就升全上下文。
  3. @MockitoBean 影响容器,意图要单一:同一类型在同一上下文里只应有一个替换方案,否则配置打架、缓存失效。
  4. Security/过滤器会改状态码:401 先分清「没登录」还是「Controller 坏了」。本站 sa-token 的未登录异常由 GlobalExceptionHandler 翻成 401,切片里可用 mockStatic 抛对应异常来驱动这条路径。
  5. 上下文缓存是性能底牌:注解组合、profile、@MockitoBean 集合、properties 任一不同都会生成新上下文。同类测试保持配置一致。
  6. Spring 测聚焦映射、校验、序列化、异常翻译:业务 if 分支已在 Mockito 层测过,别重复断言同一行代码。

你可能会懵的地方

  • 为什么 @WebMvcTest 起不来,报缺 Bean? 切片只加载 Controller、Advice、Filter 一类 Web 组件;Controller 依赖的 Service 默认不存在,需要 @MockitoBean。这是设计,不是损坏。

  • @SpringBootTest 一定要起端口吗? 默认 MOCK 环境不监听真实端口,MockMvc 直接进 DispatcherServlet;要真 HTTP 客户端时才用 RANDOM_PORT + TestRestTemplate/WebTestClient。日常契约测 MockMvc 足够。

  • 和上一课的 Mockito 测试重复吗? 不重复。上一课测「Service 算法对不对」,本课测「请求怎么进到 Service、响应怎么写出去」。两层都要,比例上单元多、Spring 测薄。

  • MyBatis-Flex 有没有像 @DataJpaTest 那样的切片? JPA 有 @DataJpaTest;MyBatis-Flex 没有官方等价切片,数据层验证用 @SpringBootTest + Testcontainers 更直接(下一课)。

  • 上下文缓存怎么「坏」的? 同一类型测试里混用不同注解组合、不同 application-test.yml 值、或 @MockitoBean 数量不一致,每个变体都起新上下文,套件时间成倍涨。排查方法:看日志出现了几次 Started CodeForgeApplication。

错法对照

错法问题正解
凡事 @SpringBootTest套件变慢没人愿跑优先切片 / 纯 Mockito
忘写 @MockitoBean 打到真依赖慢或环境报错显式替换每个外部依赖
401 就以为 Controller 坏了误诊先分清 sa-token 未登录与业务异常
每个类一套独特注解组合缓存全失效统一配置,集中同类测试
切片里连真 MySQL/Redis环境依赖、并行互踩外部依赖交给 Testcontainers

卡点急救

  1. 切片报 NoSuchBeanDefinitionException:逐个把 Controller 构造器依赖加 @MockitoBean;仍缺的自定义组件用 @Import(Xxx.class) 或 @TestConfiguration 补。
  2. 测试里 sa-token 报上下文不存在:Controller 调了 StpUtil 静态方法,用 mockStatic(StpUtil.class) 提供登录 ID 或让它抛 NotLoginException。
  3. 套件突然变慢:数日志里 Started CodeForgeApplication 出现次数;超过预期就找「新加的注解组合」,把它与同类测试对齐。

面试常问

  • @WebMvcTest 和 @SpringBootTest 怎么选? 看要证明什么:HTTP 契约(状态码、JSON、异常翻译)用切片,快且聚焦;多 Bean 装配、跨层协作、过滤器链用全上下文。成本从低到高,能低不高。

  • 上下文缓存是什么?对测试套件意味着什么? Spring Boot 按「配置指纹」(注解组合、profile、Bean 替换集合)缓存应用上下文,相同指纹的测试类复用同一份。意味着同类测试应保持配置一致——配置多样性直接等于启动次数乘上启动成本。

  • @MockitoBean 和 @Mock 的本质区别? @Mock 是 Mockito 层的对象,不进容器,靠 @InjectMocks 手工装配;@MockitoBean 是 Spring 测试模块的 Bean 覆盖机制,替换容器内同类型 Bean,Boot 3.4 起取代废弃的 @MockBean。一个管「类的装配」,一个管「容器的装配」。

自测三问

  1. 什么情况下你会选 @WebMvcTest 而不是纯 Mockito?反过来呢?
  2. @Mock 与 @MockitoBean 的关键差别是什么?
  3. 套件里 Started CodeForgeApplication 出现了 8 次,说明什么?

小结一句话

切片测一层、全测验装配;@MockitoBean 在容器里换假 Bean——按问题选成本,靠上下文缓存省时间。

延伸

  • Spring Boot Testing 官方文档
  • Spring Framework Testing
  • 下一课:test-mockmvc 专练 MockMvc 的 HTTP 断言手法,把判题接口的契约测全。
加载本节练习…
← 上一课Mockito 与 Mock下一课 →MockMvc 接口测试