bigywater.tech
Spring · 151

@MockBean 在 Boot 4 里去哪了:测试注解迁移对照

Spring Boot 4 里 @MockBean 和 @SpyBean 没了,换成 @MockitoBean 和 @MockitoSpyBean。属性有什么变化?行为一致吗?哪些写法迁移后测试还是绿的、却已经不再 mock?这篇在 3.5.16 与 4.0.8 上用同一份测试代码逐项实测。

false
把 @MockitoBean(types = UserService.class) 写在 @TestConfiguration 类上,4.0.8 里 UserService 不是 mock(3.5.16 里 @MockBean 同样的写法是 mock)
value
@MockBean 的 value 是 Class[],@MockitoBean 的 value 是 String(Bean 名字),@MockBean(UserService.class) 直接替换会编译不过
enforceOverride
4.0.8 才有的属性,为 true 时容器里没有该类型就加载失败

属性

实测 · 本机执行输出
  3.5.16 @MockBean:java.lang.String name()、java.lang.Class<?>[] value()、java.lang.Class<?>[] classes()、java.lang.Class<?>[] extraInterfaces()、org.mockito.Answers answer()、boolean serializable()、org.springframework.boot.test.mock.mockito.MockReset reset()
  3.5.16 @SpyBean:java.lang.String name()、java.lang.Class<?>[] value()、java.lang.Class<?>[] classes()、org.springframework.boot.test.mock.mockito.MockReset reset()、boolean proxyTargetAware()
  4.0.8 @MockitoBean:java.lang.String value()、java.lang.String name()、java.lang.Class<?>[] types()、java.lang.String contextName()、java.lang.Class<?>[] extraInterfaces()、org.mockito.Answers answers()、boolean serializable()、org.springframework.test.context.bean.override.mockito.MockReset reset()、boolean enforceOverride()
  4.0.8 @MockitoSpyBean:java.lang.String value()、java.lang.String name()、java.lang.Class<?>[] types()、java.lang.String contextName()、org.springframework.test.context.bean.override.mockito.MockReset reset()

value 改成 String,classes 改名 types,answer 改名 answers,新增 contextName、enforceOverride。

一致的行为

实测 · 本机执行输出
  -- T1_Stub(给 Bean 打桩,桩会不会带到下一个测试)
    3.5.16:第一个测试里给 name(1) 打桩 → controller 返回 stubbed | 第二个测试里没打桩 → controller 返回 null(第一个测试的桩有没有被带过来)
    4.0.8:第一个测试里给 name(1) 打桩 → controller 返回 stubbed | 第二个测试里没打桩 → controller 返回 null(第一个测试的桩有没有被带过来)
  -- T2_Create(容器里本来没有的类型)
    3.5.16:容器里原本没有 Missing 的 Bean,@MockBean 之后 getBeansOfType(Missing) 的个数 = 1
    4.0.8:容器里原本没有 Missing 的 Bean,@MockitoBean 之后 getBeansOfType(Missing) 的个数 = 1
  -- T4_ByName(按名字替换其中一个)
    3.5.16:按名字 englishGreeter 替换后:GreetUser.run() = mocked-hi;chineseGreeter.hi() = 你好
    4.0.8:按名字 englishGreeter 替换后:GreetUser.run() = mocked-hi;chineseGreeter.hi() = 你好
  -- T5_Spy(Spy 包住真实 Bean)
    3.5.16:Spy:controller 返回 user-2(走了真实方法),verify(svc).name(2) 通过 | Spy 上改写 name(3) → controller 返回 spied,name(4) 仍然走真实方法 → user-4
    4.0.8:Spy:controller 返回 user-2(走了真实方法),verify(svc).name(2) 通过 | Spy 上改写 name(3) → controller 返回 spied,name(4) 仍然走真实方法 → user-4

打桩重置、凭空创建、按名字替换、Spy 两边一致。

不一致

实测 · 本机执行输出
  -- T3_TwoBeans(两个同类型 Bean、字段没写名字)
    3.5.16:(没有输出)  [退出码 1: java.lang.IllegalStateException: Unable to register mock bean demo.Greeter expected a single matching bean to replace but found [chineseGreeter, englishGreeter]]
    4.0.8:(没有输出)  [退出码 1: java.lang.IllegalStateException: Unable to select a bean to override: found 2 beans of type demo.Greeter (as required by field 'T3_TwoBeans.g'): [chineseGreeter, englishGreeter]]
  -- T6_ClassLevel(写在测试类上的类级别用法)
    3.5.16:类级别 @MockBean(classes = UserService.class):UserService 是 mock?true,controller 返回 null
    4.0.8:类级别 @MockitoBean(types = UserService.class):UserService 是 mock?true,controller 返回 null
  -- T7_OnConfig(写在 @TestConfiguration 类上)
    3.5.16:注解写在 @TestConfiguration 类上:UserService 是 mock?true
    4.0.8:注解写在 @TestConfiguration 类上:UserService 是 mock?false

同类型多个 Bean 报错信息不同;写在 @TestConfiguration 类上 4.0 静默不生效。

enforceOverride

实测 · 本机执行输出
  4.0.8 @MockitoBean(enforceOverride = true) 加在容器里没有的类型上:退出码 1; java.lang.IllegalStateException: Unable to override bean: there are no beans of type demo.Missing (as required by field 'T8_Enforce.m'). If the bean is defined in a @Bean method, make sure the return type is the most specific type possible (for example, the concrete implementation type).
  对照:不加 enforceOverride(默认 false)同样的字段:退出码 0;容器里原本没有 Missing 的 Bean,@MockitoBean 之后 getBeansOfType(Missing) 的个数 = 1

要求必须替换已有的 Bean,防止凭空新建。

分步演示

MockitoBean
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(对比版本 Spring Boot 3.5.16 的 @MockBean / @SpyBean 与 4.0.8(spring-test 7.0.9)的 @MockitoBean / @MockitoSpyBean;每个测试类用 JUnit Platform Console Launcher 在独立 JVM 里跑,3.5.16 与 4.0.8 用同一份测试代码,只替换注解名与 import;属性清单来自 javap)
    • 没有验证:@Nested 测试类里的行为;MockMvc 与 @WebMvcTest 里的用法;@MockitoBean 的 contextName 与多个上下文层级;proxyTargetAware 对应的行为;@MockitoBean 与 @TestConfiguration 之外的写法(例如写在父类或接口上);上下文缓存键的差异;Mockito 版本差异
    京ICP备2026067502号-1京公网安备11010502063487号