Spring Boot测试中@MockBean无法注入被测组件的原因排查
出现这种情况主要有两个核心原因:
Autowired注入的候选bean选择逻辑冲突
ApplicationEventPublisher是Spring ApplicationContext的核心接口,而GenericWebApplicationContext本身就实现了这个接口,所以容器中默认存在一个类型为ApplicationEventPublisher的内置bean(即上下文实例)。当你通过@MockBean创建Mock对象时,容器中会同时存在两个同类型的bean:内置的上下文实例和你的Mock对象。
Spring的@Autowired默认先按类型匹配,当存在多个同类型候选bean时,会尝试按字段名称匹配。你的Service中字段名为applicationEventPublisher,但默认情况下@MockBean生成的bean名称是变量名mockApplicationEventPublisher,而非applicationEventPublisher,因此Spring最终选择了内置的上下文实例进行注入。Lombok构造函数注解的干扰
你的Service同时标注了@NoArgsConstructor和@RequiredArgsConstructor,但applicationEventPublisher字段既不是final也没有@NonNull修饰,导致@RequiredArgsConstructor生成的构造函数不包含该字段,最终Spring只能通过无参构造函数创建Service实例,再进行字段注入——这种注入方式更容易受到多候选bean的影响,无法优先选择Mock对象。
推荐采用以下两种方案解决:
方案一:改用构造函数注入(推荐)
修改Service代码,将applicationEventPublisher改为final字段,仅保留@RequiredArgsConstructor,让Spring通过构造函数注入依赖,避免字段注入的不确定性:
@Service("ILiveOrderHandlerImpl") @RequiredArgsConstructor public class ILiveOrderHandlerImpl implements ILiveOrderHandler { private final ApplicationEventPublisher applicationEventPublisher; }
构造函数注入是Spring官方推荐的注入方式,它能明确依赖关系,同时确保测试中的@MockBean会被优先注入到Service实例中。
方案二:指定MockBean的名称
如果不想修改Service代码,可以在测试类的@MockBean上指定name属性,让其bean名称与Service中的字段名一致:
@MockBean(name = "applicationEventPublisher") ApplicationEventPublisher mockApplicationEventPublisher;
这样Spring在字段注入时,会按名称匹配到这个Mock对象,而非内置的上下文实例。
内容的提问来源于stack exchange,提问作者Kannan J

