You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 23:35:32