知識庫 / Spring / Spring Boot RSS 訂閱

禁止 ApplicationRunner 或 CommandLineRunner Bean 在 JUnit 測試期間執行

Spring Boot,Testing
HongKong
6
01:02 PM · Dec 06 ,2025

1. 概述

本教程將演示如何防止具有類型為 <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/ApplicationRunner.html">ApplicationRunner</a><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/CommandLineRunner.html">CommandLineRunner</a> 的 Bean 在 Spring Boot 集成測試期間運行。

2. 示例應用程序

我們的示例應用程序包含命令行運行器、應用程序運行器和任務服務 Bean。

命令行運行器通過調用任務服務中的 execute 方法,在應用程序啓動時執行任務。

@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
    private TaskService taskService;

    public CommandLineTaskExecutor(TaskService taskService) {
        this.taskService = taskService;
    }

    @Override
    public void run(String... args) throws Exception {
        taskService.execute("command line runner task");
    }
}

同樣地,應用程序運行器通過與任務服務交互來執行另一個任務:

@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
    private TaskService taskService;

    public ApplicationRunnerTaskExecutor(TaskService taskService) {
        this.taskService = taskService;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        taskService.execute("application runner task");
    }
}

最後,任務服務負責執行其客户端的任務:

@Service
public class TaskService {
    private static Logger logger = LoggerFactory.getLogger(TaskService.class);

    public void execute(String task) {
        logger.info("do " + task);
    }
}

此外,我們還擁有一個 Spring Boot 應用程序類,用於協調所有操作:

@SpringBootApplication
public class ApplicationCommandLineRunnerApp {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationCommandLineRunnerApp.class, args);
    }
}

3. 驗證預期行為

ApplicationRunnerTaskExecutorCommandLineTaskExecutor 在 Spring Boot 加載應用程序上下文後執行。

我們可以通過一個簡單的測試來驗證這一點:

@SpringBootTest
class RunApplicationIntegrationTest {
    @SpyBean
    ApplicationRunnerTaskExecutor applicationRunnerTaskExecutor;
    @SpyBean
    CommandLineTaskExecutor commandLineTaskExecutor;

    @Test
    void whenContextLoads_thenRunnersRun() throws Exception {
        verify(applicationRunnerTaskExecutor, times(1)).run(any());
        verify(commandLineTaskExecutor, times(1)).run(any());
    }
}

正如我們所見,我們正在使用 SpyBean 註解來將 Mockito 間諜應用於 ApplicationRunnerTaskExecutorCommandLineTaskExecutor bean 時。 這樣做,我們可以驗證這些 bean 中的 run 方法被調用了一次。

在接下來的部分,我們將看到在我們的 Spring Boot 集成測試中防止此默認行為的各種方法和技術。

4. 通過 Spring 配置文件進行預防

通過使用 @Profile 標註它們,我們可以防止這兩個類同時運行:

@Profile("!test")
@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
    // same as before
}
@Profile("!test")
@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
    // same as before
}

在上述更改之後,我們繼續進行集成測試:

@ActiveProfiles("test")
@SpringBootTest
class RunApplicationWithTestProfileIntegrationTest {
    @Autowired
    private ApplicationContext context;

    @Test
    void whenContextLoads_thenRunnersAreNotLoaded() {
        assertNotNull(context.getBean(TaskService.class));
        assertThrows(NoSuchBeanDefinitionException.class, 
          () -> context.getBean(CommandLineTaskExecutor.class), 
          "CommandLineRunner should not be loaded during this integration test");
        assertThrows(NoSuchBeanDefinitionException.class, 
          () -> context.getBean(ApplicationRunnerTaskExecutor.class), 
          "ApplicationRunner should not be loaded during this integration test");
    }
}

正如我們所見,我們已對上述測試類進行了標註,使用了 @ActiveProfiles(“test”) 註解,這意味着它不會將使用 @Profile(“!test”) 註解標註的組件進行依賴注入。因此,CommandLineTaskExecutor Bean 和 ApplicationRunnerTaskExecutor Bean 都未被加載。

5. 通過 ConditionalOnProperty 註解進行預防

或者,我們可以通過配置屬性來配置它們的 wiring,然後使用 ConditionalOnProperty 註解:

@ConditionalOnProperty(
  prefix = "application.runner", 
  value = "enabled", 
  havingValue = "true", 
  matchIfMissing = true)
@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
    // same as before
}
@ConditionalOnProperty(
  prefix = "command.line.runner", 
  value = "enabled", 
  havingValue = "true", 
  matchIfMissing = true)
@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
    // same as before
}

正如我們所見,ApplicationRunnerTaskExecutorCommandLineTaskExecutor 默認已啓用,如果我們將以下屬性設置為 false,則可以禁用它們:

  • command.line.runner.enabled
  • application.runner.enabled

因此,在我們的測試中,我們將這些屬性設置為 false,因此 neither ApplicationRunnerTaskExecutor nor CommandLineTaskExecutor bean 沒有加載到應用程序上下文中

@SpringBootTest(properties = { 
  "command.line.runner.enabled=false", 
  "application.runner.enabled=false" })
class RunApplicationWithTestPropertiesIntegrationTest {
    // same as before
}

現在,儘管上述技術幫助我們實現目標,但在某些情況下,我們希望測試所有 Spring Bean 是否已加載並正確綁定。

例如,我們可能希望測試 TaskService Bean 是否已正確注入到 CommandLineTaskExecutor 中,但我們仍然不想在其 run 方法在我們的測試期間執行。 讓我們看看解釋如何實現這一點的最後一部分。

6. 通過避免完全啓動容器進行預防

這裏我們將描述如何通過不完全啓動整個應用程序容器來防止 <em >CommandLineTaskExecutor </em ><em >ApplicationRunnerTaskExecutor </em > Bean 執行。

在之前的章節中,我們使用了 <em >@SpringBootTest</em > 註解,這導致了在集成測試期間整個容器被啓動。 <em >@SpringBootTest</em > 包含兩個與該解決方案相關的元註解:

@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)

如果我們的測試不需要啓動整個容器,那麼就不需要使用@BootstrapWith

相反,我們可以用@ContextConfiguration替換它:

@ContextConfiguration(classes = {ApplicationCommandLineRunnerApp.class},
  initializers = ConfigDataApplicationContextInitializer.class)

通過使用 @ContextConfiguration, 我們確定用於集成測試如何加載和配置應用程序上下文的方式。通過設置ContextConfiguration 屬性,我們聲明 Spring Boot 應使用 ApplicationCommandLineRunnerApp 類來加載應用程序上下文。通過定義初始化器為 ConfigDataApplicationContextInitializer,應用程序加載其屬性。

我們仍然需要 @ExtendWith(SpringExtension.class),因為這與 JUnit 5 的 Jupiter 編程模型集成 Spring TestContext Framework。

結果是,上述內容導致 Spring Boot 應用程序上下文加載應用程序的組件和屬性,而不會執行 CommandLineTaskExecutorApplicationRunnerTaskExecutor 豆。

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationCommandLineRunnerApp.class }, 
  initializers = ConfigDataApplicationContextInitializer.class)
public class LoadSpringContextIntegrationTest {
    @SpyBean
    TaskService taskService;

    @SpyBean
    CommandLineRunner commandLineRunner;

    @SpyBean
    ApplicationRunner applicationRunner;

    @Test
    void whenContextLoads_thenRunnersDoNotRun() throws Exception {
        assertNotNull(taskService);
        assertNotNull(commandLineRunner);
        assertNotNull(applicationRunner);

        verify(taskService, times(0)).execute(any());
        verify(commandLineRunner, times(0)).run(any());
        verify(applicationRunner, times(0)).run(any());
    }
}

此外,我們還需要注意,當 ConfigDataApplicationContextInitializer 被單獨使用時,它不提供對 @Value(“${…}”) 註解注入的支持。 如果需要支持它,則必須配置 PropertySourcesPlaceholderConfigurer

7. 結論

在本文中,我們展示了在 Spring Boot 集成測試中阻止執行 ApplicationRunnerCommandLineRunner 豆子的各種方法。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.