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. 驗證預期行為
ApplicationRunnerTaskExecutor 和 CommandLineTaskExecutor 在 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 間諜應用於 ApplicationRunnerTaskExecutor 和 CommandLineTaskExecutor 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
}正如我們所見,ApplicationRunnerTaskExecutor 和 CommandLineTaskExecutor 默認已啓用,如果我們將以下屬性設置為 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 應用程序上下文加載應用程序的組件和屬性,而不會執行 CommandLineTaskExecutor 或 ApplicationRunnerTaskExecutor 豆。
@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 集成測試中阻止執行 ApplicationRunner 和 CommandLineRunner 豆子的各種方法。