如何用Mockito模拟SpringApplication.run测试SpringBoot主类逻辑?
问题描述
我的SpringBoot主类中包含代码:
ConfigurableApplicationContext applicationContext = SpringApplication.run(MyApplication.class, args);
测试这段代码时,每次都会实际启动应用、连接数据库,导致测试效率低下。我希望找到简单的模拟方式,仅测试参数校验、根据processingType调用Controller对应方法的逻辑,以此提升代码覆盖率。
当前使用技术栈:
- SpringBoot 2.7.1
- Mockito 4.5.1
- JUnit Jupiter 5.8.2
以下是我尝试的测试代码:
@SpringBootTest public class ApplicationTests { @Mock private ApplicationUtils utils; @Mock private ApplicationContext context; @Mock private Controller controller; @Mock private SpringApplication springApplication; @Mock private Environment env; @InjectMocks private Application app; @BeforeEach public void init() { MockitoAnnotations.openMocks(this); } @Test void applicationTestErroParametro3() throws Exception { String[] args = {"asd","asd","8"}; int statusCode = catchSystemExit(() -> { app.main(args); verify(utils, atLeast(1)).validateParameters(args); doThrow(new BusinessException("invalid argmuents")).when(utils).validateParameters(args); }); assertEquals(99, statusCode); } @Test void applicationTestParameter1() throws Exception { String[] args = {"1","1","1"}; when(springApplication.run()).thenReturn(applicationContext); when(context.getEnvironment()).thenReturn(env); int statusCode = catchSystemExit(() -> { doNothing().when(controller).loadFile(); app.main(args); verify(utils, atLeast(1)).validateParameters(args); }); assertEquals(1, statusCode); } }
解决方案
你的核心问题在于SpringApplication.run是静态方法,直接Mock对象无法拦截它的调用,且@SpringBootTest本身会启动Spring容器,导致测试时实际加载应用。可以通过以下步骤解决:
1. 重构主类,分离启动与业务逻辑
把主类中的业务逻辑(参数校验、Controller调用)从main方法抽离,避免和容器启动代码耦合:
@SpringBootApplication public class MyApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args); // 抽离业务逻辑到单独方法,传入上下文和参数 processBusinessLogic(context, args); } // 可测试的业务方法 static void processBusinessLogic(ApplicationContext context, String[] args) { ApplicationUtils utils = context.getBean(ApplicationUtils.class); // 参数校验逻辑 utils.validateParameters(args); String processingType = args[0]; // 假设processingType为第一个参数 Controller controller = context.getBean(Controller.class); // 根据类型调用对应方法 if ("1".equals(processingType)) { controller.loadFile(); } // 其他业务逻辑... } }
2. 编写单元测试,无需启动Spring容器
用@ExtendWith(MockitoExtension.class)替代@SpringBootTest,直接Mock依赖测试抽离的业务方法:
@ExtendWith(MockitoExtension.class) public class MyApplicationTests { @Mock private ApplicationUtils utils; @Mock private Controller controller; @Mock private ApplicationContext context; @Test void testInvalidParameters() { String[] args = {"asd", "asd", "8"}; // 模拟参数校验抛出异常 doThrow(new BusinessException("invalid arguments")).when(utils).validateParameters(args); // 模拟上下文获取工具类实例 when(context.getBean(ApplicationUtils.class)).thenReturn(utils); // 验证异常抛出 assertThrows(BusinessException.class, () -> { MyApplication.processBusinessLogic(context, args); }); // 验证校验方法被调用 verify(utils, times(1)).validateParameters(args); } @Test void testValidParametersAndCallLoadFile() { String[] args = {"1", "1", "1"}; // 模拟参数校验通过 doNothing().when(utils).validateParameters(args); // 模拟上下文获取依赖实例 when(context.getBean(ApplicationUtils.class)).thenReturn(utils); when(context.getBean(Controller.class)).thenReturn(controller); // 执行业务方法 MyApplication.processBusinessLogic(context, args); // 验证逻辑执行 verify(utils, times(1)).validateParameters(args); verify(controller, times(1)).loadFile(); } }
3. 若需测试main方法的容器启动逻辑(可选)
如果一定要测试main方法,可用Mockito的mockStatic拦截SpringApplication.run的调用,避免实际启动容器:
@ExtendWith(MockitoExtension.class) public class MyApplicationMainTest { @Mock private ConfigurableApplicationContext context; @Mock private ApplicationUtils utils; @Mock private Controller controller; @Test void testMainMethod() { String[] args = {"1", "1", "1"}; // 模拟SpringApplication.run返回Mock上下文 try (MockedStatic<SpringApplication> mockedSpringApp = Mockito.mockStatic(SpringApplication.class)) { mockedSpringApp.when(() -> SpringApplication.run(MyApplication.class, args)) .thenReturn(context); // 模拟上下文获取依赖 when(context.getBean(ApplicationUtils.class)).thenReturn(utils); when(context.getBean(Controller.class)).thenReturn(controller); doNothing().when(utils).validateParameters(args); // 调用main方法 MyApplication.main(args); // 验证调用逻辑 mockedSpringApp.verify(() -> SpringApplication.run(MyApplication.class, args), times(1)); verify(utils, times(1)).validateParameters(args); verify(controller, times(1)).loadFile(); } } }
关键说明
- 不要用
@SpringBootTest测纯业务逻辑:该注解会启动完整Spring容器,适合集成测试而非单元测试。 - 静态方法Mock注意事项:
MockedStatic要放在try-with-resources块中,确保资源自动释放,避免影响其他测试。 - 重构是最优解:业务逻辑与容器启动解耦,既提升可测试性,也让代码结构更清晰。
内容的提问来源于stack exchange,提问作者Arthur Borges
相关产品推荐
相关产品推荐

