Spring Boot 4 里 @MockBean 和 @SpyBean 没了,换成 @MockitoBean 和 @MockitoSpyBean。属性有什么变化?行为一致吗?哪些写法迁移后测试还是绿的、却已经不再 mock?这篇在 3.5.16 与 4.0.8 上用同一份测试代码逐项实测。
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 静默不生效。
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,防止凭空新建。