升级 Boot 4 后,我的 mock 全红了

上周把项目升到 4.1.1,改完 pom 跑 mvn test,红了一片。

不是断言挂,是编译不过。

ini 复制代码
[ERROR] OldMockBeanTest.java:[5,50] 程序包 org.springframework.boot.test.mock.mockito 不存在
[ERROR] OldMockBeanTest.java:[10,6] 找不到符号
  符号: 类 MockBean

@MockBean 找不到了。

我把 spring-boot-test-4.1.1.jar 解开看了一眼,org/springframework/boot/test/mock/mockito/ 这个包整个没了,BOM 里也翻不到任何跟 mockito 有关的 Boot 模块。

它跑到 spring-test 去了

新注解在 Spring Framework 里。spring-test-7.0.9.jar 的 org/springframework/test/context/bean/override/mockito/ 下面有 MockitoBean、MockitoBeans、MockitoSpyBean、MockitoSpyBeans、MockReset,还有一个 MockitoBeanOverrideProcessor。

这就带出一个麻烦:名字改了,import 也得跟着改。

依赖树也一点没变。我对比过 3.5.16 和 4.1.1 的 spring-boot-starter-test,两边都是 16 个依赖,逐项一样;动的是 spring-boot-test-autoconfigure 那个 jar------3.5.16 里面有 13 个 autoconfigure 子包,4.1.1 只剩 2 个。

所以升级完别指望"看看依赖缺没缺"能发现问题,直接编译,让它报错。

改法就两处

java 复制代码
// 旧(Boot 3)
import org.springframework.boot.test.mock.mockito.MockBean;
@MockBean
DiscountClient discountClient;

// 新(Boot 4)
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean
DiscountClient discountClient;

有个地方要留神:IDE 自动补全有时候会把旧的静态导入又给你补回来(这条我没复现过,算经验之谈)。真遇到"编译过了但真实服务还是被调了",先全局搜 boot.test.mock.mockito,搜到就是没换干净。

我没信文档,写了个小工程试

场景很简单:PriceService 依赖 DiscountClient,真实实现 RemoteDiscountClient 一被调用就抛异常(假装远程服务挂了)。要是 mock 没替换成功,测试会立刻炸,不用猜。

java 复制代码
@SpringBootTest
class MockitoBeanTest {
    @MockitoBean     DiscountClient discountClient;
    @Autowired       PriceService priceService;

    @Test void mockReplacesBean() {
        given(discountClient.discountPercent(7L)).willReturn(20);
        assertThat(priceService.total(7L, 2)).isEqualTo(160);   // 100*0.8*2
    }
}

@SpringBootTest
class MockitoSpyBeanTest {
    @MockitoBean     DiscountClient discountClient;
    @MockitoSpyBean  PriceService priceService;
    @Autowired       DiscountClient injected;

    @Test void spyKeepsRealLogic() {
        given(discountClient.discountPercent(1L)).willReturn(0);
        assertThat(priceService.total(1L, 3)).isEqualTo(300);   // 真实计算
        verify(discountClient).discountPercent(1L);             // 调用被记录
    }

    @Test void spyCanStub() {
        given(priceService.isVip(1L)).willReturn(false);        // 只盖这一个方法
        assertThat(priceService.isVip(1L)).isFalse();           // 真逻辑 userId<1000 会返回 true
    }
}

跑完看三行输出:

  • [@MockitoBean] total(7,2) = 160,桩生效了,真实客户端一次都没被调(调了就抛异常);
  • 注入的是不是 mock 实例: true,说明它换的是容器里的 bean,不是给你字段塞个新对象;
  • 打桩后 isVip(1) = false,说明 spy 留着真 bean,只盖你点名的那个方法。

三个测试全绿,一次过。

三个场景别写混

我这次三个都碰到了,区别就在"上下文里到底有没有这个 bean"。@WebMvcTest 切片里 Service 根本不存在,@MockitoBean 是补一个上去,不写上下文起不来;@SpringBootTest 里真实 bean 在,但它会发网络请求,@MockitoBean 把它换掉。

要保留真实逻辑、只想固定某几个方法或者统计调用次数,才用 @MockitoSpyBean。

另外 javap 里还看到两个属性,@MockReset(BEFORE / AFTER / NONE)和 @MockitoBean(enforceOverride = ...)。这两个我只看到了签名,行为没试过,你要用先在测试里跑一遍再信。

再记一句我自己的规矩:一个测试里替换到第四个 bean,就别硬测了,那个类该拆。@MockitoBean 太好用,很容易把"依赖五个外部服务"的类测成绿的,实际调用链一条都没验。

编译过了但行为不对,先查两处

报错缺包最好办,真正费时间的是"编译通过、跑起来不对"。我一般先查两个地方:

一是搜 import,看还有没有 boot.test.mock.mockito 残留;二是看注入进来的到底是不是同一个对象------这次我用 injected 和 discountClient 做了同一性判断,输出 true 才叫替换成功。

两步都对还不对,那就是上下文装配的问题,回去看 @MockitoBean 有没有落在字段上。它换的是容器里的 bean,不是给某个方法塞参数。

清单

按类名搜 @MockBean、@SpyBean(搜文本会漏掉静态导入),改名,换 import,不用补依赖(spring-test 跟着 spring-boot-starter-test 已经在 classpath 上了),然后全量跑一遍。桩在方法之间共享的话,顺手确认下重置策略。


工程:Spring Boot 4.1.1 / JDK 17.0.12 / Maven 3.9.16。这是《Boot 4 迁移实录》第 2 篇,上一篇是测试切片拆成 106 个模块那次。

相关推荐
学长毕设3 小时前
基于SpringBoot的野生动物公益保护平台设计与实现-附源码
java·spring boot·后端
她的男孩4 小时前
三人会签只批了一个就往下走:Forge Admin 把 all / any / ratio 写成 completionCondition
spring boot·低代码·工作流引擎
vipxieliang6 小时前
ValidX vs 手写验证代码:开发效率对比实测
java·spring boot·后端
vipxieliang6 小时前
虚拟线程与 GraalVM:Spring Boot 3.x 云原生性能跃迁
java·spring boot·后端
头发还在的女程序员7 小时前
基于 Spring Boot + Vue 3 + uni-app 的灵活用工系统:四端架构与业务流程设计
vue.js·spring boot·uni-app·灵活用工·灵工系统
CoderYanger13 小时前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第二弹)
java·数据库·spring boot·spring·面试·java-ee·mybatis
计算机毕设定制辅导-无忧学长1 天前
《基于微信小程序的居家医疗陪护预约系统设计与实现》
java·spring boot·微信小程序·uniapp·居家医疗陪护预约系统
小郎君6581 天前
注解式脱敏:用 Jackson 的 ContextualSerializer 做字段级策略(一)
spring boot·spring cloud
jktantan1 天前
注解式脱敏:用 Jackson 的 ContextualSerializer 做字段级策略(一)
spring boot·后端·算法·spring cloud·系统架构