Spring Boot集成测试中Mockito的mockitoInterceptor被替换致NPE问题排查
问题排查与解决方案
核心原因分析
你的问题本质是Spring测试上下文里的ConfigurationService Mock实例被意外替换,原本绑定的Mockito存根只对旧实例生效,新实例无存根逻辑所以返回null触发NPE。常见触发场景:
1. Spring AOP代理重建/覆盖
如果ConfigurationService带有@Transactional、@Cacheable这类Spring AOP注解,Spring会给MockBean套一层AOP代理。若测试过程中事务上下文切换、缓存初始化等操作触发了AOP代理重建,新生成的代理会脱离原有的Mockito存根绑定。
2. Bean被重新实例化
ConfigurationService被定义为prototype作用域,每次注入都会生成新实例,你仅给第一个实例做了存根,后续实例自然无匹配逻辑;- 存在自定义
BeanPostProcessor/BeanFactoryPostProcessor,或某些自动配置类在测试执行中激活,替换了@MockBean创建的Mock实例; @MockBean的优先级被覆盖,Spring重新创建了原生的ConfigurationService实例。
排查步骤
- 验证实例一致性:在两个Service调用
ConfigurationService前后,打印实例的hashCode(),若hashCode不同,说明实例已被替换; - 查看Spring Debug日志:开启Spring的
debug级别日志,搜索ConfigurationService相关的Bean创建、初始化日志,查找是否有"Replacing existing bean"类提示; - 检查Bean作用域:确认
ConfigurationService类上是否有@Scope("prototype"),或配置文件中是否修改了它的作用域; - 排查AOP注解:检查
ConfigurationService及其方法上的AOP注解,确认是否存在触发代理重建的逻辑。
解决方案
方案1:强制MockBean优先级
用@Primary标记@MockBean,确保Spring优先使用Mock实例,避免被原生Bean替换:
@MockBean @Primary private ConfigurationService configurationService;
方案2:手动注册MockBean(绕过自动代理问题)
放弃@MockBean,手动创建Mock并注册为Spring Bean,确保AOP代理直接包装Mockito实例:
@Configuration static class TestConfig { @Bean @Primary public ConfigurationService configurationService() { ConfigurationService mock = Mockito.mock(ConfigurationService.class); // 提前完成存根绑定 Mockito.when(mock.yourMethod1(any())).thenReturn(yourResult1); Mockito.when(mock.yourMethod2(any())).thenReturn(yourResult2); return mock; } }
方案3:修复作用域问题
如果是prototype作用域导致多实例,要么将作用域改回singleton,要么在每次注入后重新绑定存根(不推荐,维护成本高)。
方案4:调整存根时机
确保存根在Mock实例完全初始化后执行,比如将存根逻辑放在@BeforeEach中而非测试类构造方法:
@BeforeEach void setUp() { Mockito.when(configurationService.yourMethod1(any())).thenReturn(yourResult1); Mockito.when(configurationService.yourMethod2(any())).thenReturn(yourResult2); }
内容的提问来源于stack exchange,提问作者kniffte
相关产品推荐
相关产品推荐

