使用Mockito测试SpringBoot启动类报ApplicationListener无法实例化错误如何解决
问题根因
你的报错本质是单元测试执行时触发了真实的SpringApplication初始化流程,程序加载了classpath下spring.factories文件中定义的ApplicationListener实现类,其中FunctionProxyApplicationListener初始化时依赖的javac工具类在单元测试的类加载环境下类型不匹配,最终抛出实例化失败异常。
你现有测试代码的核心问题是:仅声明了SpringApplicationBuilder的mock实例,但没有拦截主类main方法中new SpringApplicationBuilder()的构造调用,导致真实的SpringApplicationBuilder对象被创建,后续走了完整的Spring启动逻辑,没有达到Mock隔离的效果。
修复方案
修改后的测试类代码如下:
import com.ab.obs.spring.ObsSpringBootApplicationForCloud; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.boot.builder.SpringApplicationBuilder; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; @RunWith(PowerMockRunner.class) @PrepareForTest({ObsDocumentApplication.class, ObsSpringBootApplicationForCloud.class, SpringApplicationBuilder.class}) public class ObsDocumentApplicationTest { @Mock private SpringApplicationBuilder applicationBuilder; @Test public void testMainClass() throws Exception { // 拦截SpringApplicationBuilder的构造调用,返回mock实例 whenNew(SpringApplicationBuilder.class).withNoArguments().thenReturn(applicationBuilder); // mock链式调用的返回,支持builder流式调用 when(applicationBuilder.sources(any(Class.class))).thenReturn(applicationBuilder); when(applicationBuilder.run(any(String[].class))).thenReturn(null); // mock静态工具类的初始化方法 PowerMockito.doNothing().when(ObsSpringBootApplicationForCloud.class, "initBase", eq(applicationBuilder), eq("csl-svc-obs-test")); // 执行测试方法 ObsDocumentApplication.main(new String[] {}); // 验证调用流程是否符合预期 verify(ObsSpringBootApplicationForCloud.class); ObsSpringBootApplicationForCloud.initBase(eq(applicationBuilder), eq("csl-svc-obs-test")); verify(applicationBuilder).sources(eq(ObsDocumentApplication.class)); verify(applicationBuilder).run(eq(new String[]{})); } @Test public void testConfigure() { ObsDocumentApplication application = new ObsDocumentApplication(); PowerMockito.doNothing().when(ObsSpringBootApplicationForCloud.class, "initBase", eq(applicationBuilder), eq("csl-svc-obs-test")); SpringApplicationBuilder result = application.configure(applicationBuilder); // 验证configure方法的逻辑 verify(ObsSpringBootApplicationForCloud.class); ObsSpringBootApplicationForCloud.initBase(eq(applicationBuilder), eq("csl-svc-obs-test")); assert result == applicationBuilder; } }
修复要点:
- 在
@PrepareForTest中加入SpringApplicationBuilder.class,支持PowerMock拦截其构造方法 - 用
whenNew拦截构造调用,返回预设的mock实例,避免创建真实对象 - 对builder的链式调用方法做mock,返回mock实例本身支持流式调用
- 增加调用逻辑校验,确保启动流程符合预期
启动类单元测试思路
- 测试边界:启动类的单元测试只需要验证启动流程的调用逻辑是否符合预期,不需要真的启动Spring上下文,真实启动Spring属于集成测试的范畴,不需要用Mockito实现
- 核心覆盖点:
- main方法是否正确创建
SpringApplicationBuilder实例 - 自定义的初始化逻辑(如代码中的
ObsSpringBootApplicationForCloud.initBase)是否被正确调用、入参是否正确 - 是否正确指定启动类作为配置源、是否正确调用run方法传入启动参数
- 重写的
configure方法逻辑是否符合预期
- main方法是否正确创建
- Mock原则:
- 仅需要拦截会触发真实Spring上下文启动的入口,不需要Mock Spring内部的所有实现类
- 对链式调用的Builder类,Mock方法时要返回自身实例,避免空指针异常
- 静态方法、构造方法的拦截需要配合PowerMock实现,提前在
@PrepareForTest中声明对应的类
内容的提问来源于stack exchange,提问作者Sitansu
相关产品推荐
相关产品推荐

