Prevent ApplicationRunner or CommandLineRunner Beans From Executing During Junit Testing – 在Junit测试中防止ApplicationRunner或CommandLineRunner Bean的执行

最后修改: 2020年 3月 27日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll show how we can prevent beans of type of ApplicationRunner or CommandLineRunner from running during Spring Boot integration tests.

在本教程中,我们将展示如何防止ApplicationRunnerCommandLineRunner类型的bean在Spring Boot集成测试中运行。

2. Example Application

2.应用实例

Our example application consists of a command-line runner, an application runner, and a task service bean.

我们的示例应用程序由一个命令行运行器、一个应用程序运行器和一个任务服务Bean组成。

The command-line runner, calls the task service’s execute method, in order to perform a task on application startup:

命令行运行器,调用任务服务的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");
    }
}

In the same manner, the application runner interacts with the task service to perform another 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");
    }
}

Finally, the task service is responsible for executing its client’s tasks:

最后,任务服务负责执行其客户的任务。

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

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

And, we’ve also got a Spring Boot application class that makes it all work:

而且,我们还有一个Spring Boot应用类,使这一切得以实现。

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

3. Testing Expected Behavior

3.测试预期的行为

The ApplicationRunnerTaskExecutor and the CommandLineTaskExecutor run after Spring Boot loads the application context.

ApplicationRunnerTaskExecutorCommandLineTaskExecutor在Spring Boot加载应用上下文后运行。

We can verify this with a simple test:

我们可以通过一个简单的测试来验证这一点。

@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());
    }
}

As we see, we’re using the SpyBean annotation for applying Mockito spies to the ApplicationRunnerTaskExecutor and CommandLineTaskExecutor beans. By doing so, we can verify that the run method of each of these beans was called one time.

正如我们所看到的,我们正在使用SpyBean注解来将Mockito间谍应用到ApplicationRunnerTaskExecutorCommandLineTaskExecutorBean。通过这样做,我们可以验证这些Bean的run方法是否被调用过一次。

In the next sections, we are going to see various ways and techniques for preventing this default behavior during our Spring Boot integration tests.

在接下来的章节中,我们将看到在Spring Boot集成测试中防止这种默认行为的各种方法和技巧。

4. Prevention via Spring Profiles

4.通过Spring剖面图进行预防

One way that we can prevent these two from running is by annotating them with @Profile:

我们可以阻止这两个人运行的一个方法是用 @Profile来注释它们。

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

After the above changes, we proceed with our integration test:

在做完上述改动后,我们继续进行我们的集成测试。

@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");
    }
}

As we see, we annotated the above test class with the @ActiveProfiles(“test”) annotation, which means it will not wire those annotated with @Profile(“!test”). As a result, neither the CommandLineTaskExecutor bean nor the ApplicationRunnerTaskExecutor bean is loaded at all.

正如我们所看到的,我们用@ActiveProfiles(“test”)注解了上述测试类,这意味着它将不会连接那些用@Profile(“!test”)注解的类。结果,CommandLineTaskExecutorBean和ApplicationRunnerTaskExecutorBean都没有被加载。

5. Prevention via the ConditionalOnProperty Annotation

5.通过ConditionalOnProperty注解进行预防

Or, we can configure their wiring by property and then use the ConditionalOnProperty annotation:

或者,我们可以通过属性来配置它们的布线,然后使用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
}

As we see, the ApplicationRunnerTaskExecutor and the CommandLineTaskExecutor are enabled by default, and we can disable them if we set the following properties to false:

正如我们所看到的,ApplicationRunnerTaskExecutorCommandLineTaskExecutor默认是启用的,如果我们将以下属性设置为false,我们可以禁用它们。

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

So, in our test, we set these properties to false and neither the ApplicationRunnerTaskExecutor nor the CommandLineTaskExecutor beans are loaded to the application context:

因此,在我们的测试中,我们将这些属性设置为falseApplicationRunnerTaskExecutorCommandLineTaskExecutorBean都没有被加载到应用程序上下文中

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

Now, although the above techniques help us to achieve our goal, there are cases where we want to test that all the Spring beans are loaded and wired correctly.

现在,尽管上述技术帮助我们实现了目标,但在有些情况下,我们想测试所有的Spring Bean是否被正确加载和连接。

For instance, we may want to test that the TaskService bean is injected correctly to the CommandLineTaskExecutor, but we still don’t want its run method to be executed during our test.  So, let’s see the last section that explains how we can achieve that.

例如,我们可能想测试TaskServicebean是否被正确地注入到CommandLineTaskExecutor中,但我们仍然不希望它的run方法在测试中被执行。 所以,让我们看看最后一节,它解释了我们如何实现这一目标。

6. Prevention by Not Bootstrapping the Entire Container

6.通过不启动整个容器进行预防

Here, we’ll describe how we can prevent the CommandLineTaskExecutor and ApplicationRunnerTaskExecutor beans from execution by not bootstrapping the entire application container.

在这里,我们将介绍如何通过不启动整个应用程序容器来防止CommandLineTaskExecutorApplicationRunnerTaskExecutor Bean的执行。

In the previous sections, we used the @SpringBootTest annotation and this resulted in the entire container to be bootstrapped during our integration tests. @SpringBootTest includes two meta-annotations that are relevant to this last solution:

在前面的章节中,我们使用了@SpringBootTest注解,这导致整个容器在我们的集成测试中被引导。@SpringBootTest 包括两个元注释,与这最后一个解决方案有关。

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

Well, if there’s no need to bootstrap the entire container during our test, then don’t want to use @BootstrapWith.

那么,如果在我们的测试中不需要引导整个容器,那么就不要使用@BootstrapWith

Instead, we can replace it with @ContextConfiguration:

相反,我们可以用@ContextConfiguration替换它。

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

With @ContextConfiguration, we determine how to load and configure the application context for integration tests. By setting the ContextConfiguration classes property, we declare that Spring Boot should use the ApplicationCommandLineRunnerApp class to load the application context. By defining the initializer to be the ConfigDataApplicationContextInitializer, the application loads its properties.

通过 @ContextConfiguration,我们决定如何为集成测试加载和配置应用程序上下文。通过设置ContextConfigurationclasses属性,我们声明Spring Boot应使用ApplicationCommandLineRunnerApp类来加载应用程序上下文。通过定义初始化器为ConfigDataApplicationContextInitializer,应用程序加载其属性./strong>。

We still need @ExtendWith(SpringExtension.class) since that integrates the Spring TestContext Framework into JUnit 5’s Jupiter programming model.

我们仍然需要@ExtendWith(SpringExtension.class),因为这将Spring TestContext框架整合到JUnit 5的Jupiter编程模型中。

As a result of the above, the Spring Boot application context loads the application’s components and properties without executing the CommandLineTaskExecutor or the ApplicationRunnerTaskExecutor beans:

由于上述原因,Spring Boot应用程序上下文在不执行CommandLineTaskExecutorApplicationRunnerTaskExecutor Bean的情况下加载应用程序的组件和属性:

@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());
    }
}

Also, we have to keep in mind that the ConfigDataApplicationContextInitializer, when it is used alone, does not provide support for @Value(“${…​}”) injection. If we want to support it we have to configure a PropertySourcesPlaceholderConfigurer.

此外,我们必须记住,ConfigDataApplicationContextInitializer,当它被单独使用时,并不提供对@Value(“${…}”)注入的支持。如果我们想支持它,我们必须配置一个 PropertySourcesPlaceholderConfigurer

7. Conclusion

7.结论

In this article, we showed various ways of preventing the execution of the ApplicationRunner and CommandLineRunner beans during Spring Boot integration tests.

在这篇文章中,我们展示了在Spring Boot集成测试期间防止执行ApplicationRunnerCommandLineRunner Bean的各种方法。

As always, the code is available over on GitHub.

像往常一样,代码可在GitHub上获得