Empêcher l'exécution des beans ApplicationRunner ou CommandLineRunner pendant les tests Junit

1. Vue d'ensemble

Dans ce didacticiel, nous montrerons comment nous pouvons empêcher les beans de type ApplicationRunner ou CommandLineRunner de s'exécuter pendant les tests d'intégration Spring Boot.

2. Exemple d'application

Notre exemple d'application se compose d'un exécuteur de ligne de commande, d'un exécuteur d'application et d'un bean de service de tâche.

Le lanceur de ligne de commande appelle la méthode d' exécution du service de tâches , afin d'effectuer une tâche au démarrage de l'application:

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

De la même manière, le programme d'exécution de l'application interagit avec le service de tâche pour effectuer une autre tâche:

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

Enfin, le service des tâches est chargé d'exécuter les tâches de son client:

@Service public class TaskService { private static Logger logger = LoggerFactory.getLogger(TaskService.class); public void execute(String task) { logger.info("do " + task); } } 

Et nous avons également une classe d'application Spring Boot qui fait tout fonctionner:

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

3. Test du comportement attendu

Le ApplicationRunnerTaskExecutor et la CommandLineTaskExecutor course après charges de démarrage Spring le contexte de l' application.

Nous pouvons le vérifier avec un 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()); } }

Comme nous le voyons, nous utilisons l' annotation SpyBean pour appliquer des espions Mockito aux beans ApplicationRunnerTaskExecutor et CommandLineTaskExecutor . Ce faisant, nous pouvons vérifier que la méthode run de chacun de ces beans a été appelée une fois.

Dans les sections suivantes, nous allons voir différentes méthodes et techniques pour empêcher ce comportement par défaut lors de nos tests d'intégration Spring Boot.

4. Prévention via Spring Profiles

Une façon d'empêcher ces deux de s'exécuter est de les annoter avec @Profile :

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

Après les modifications ci-dessus, nous procédons à notre test d'intégration:

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

Comme nous le voyons, nous avons annoté la classe de test ci-dessus avec l' annotation @ActiveProfiles («test») , ce qui signifie qu'elle ne reliera pas celles annotées avec @Profile («! Test») . Par conséquent, ni le bean CommandLineTaskExecutor ni le bean ApplicationRunnerTaskExecutor ne sont chargés du tout.

5. Prévention via l' annotation ConditionalOnProperty

Ou, nous pouvons configurer leur câblage par propriété, puis utiliser l' 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 }

Comme on le voit, la ApplicationRunnerTaskExecutor et CommandLineTaskExecutor sont activées par défaut, et nous pouvons les désactiver si nous fixons les propriétés suivantes à faux :

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

Ainsi, dans notre test, nous définissons ces propriétés sur false et ni le ApplicationRunnerTaskExecutor ni les beans CommandLineTaskExecutor ne sont chargés dans le contexte de l'application :

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

Maintenant, bien que les techniques ci-dessus nous aident à atteindre notre objectif, il y a des cas où nous voulons tester que tous les beans Spring sont correctement chargés et câblés.

Par exemple, on peut vouloir tester que le TaskService haricot est injecté correctement à l' CommandLineTaskExecutor, mais nous ne voulons pas encore sa course méthode à exécuter lors de notre test. Alors, voyons la dernière section qui explique comment nous pouvons y parvenir.

6. Prévention en ne démarrant pas l'intégralité du conteneur

Ici, nous allons décrire comment nous pouvons empêcher l' exécution des beans CommandLineTaskExecutor et ApplicationRunnerTaskExecutor en ne démarrant pas l'intégralité du conteneur d'application.

Dans les sections précédentes, nous avons utilisé l' annotation @SpringBootTest et cela a abouti à l'amorçage du conteneur entier lors de nos tests d'intégration. @SpringBootTest comprend deux méta-annotations pertinentes pour cette dernière solution:

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

Eh bien, s'il n'est pas nécessaire d'amorcer tout le conteneur pendant notre test, alors ne voulez pas utiliser @BootstrapWith .

Au lieu de cela, nous pouvons le remplacer par @ContextConfiguration :

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

Avec @ContextConfiguration, nous déterminons comment charger et configurer le contexte d'application pour les tests d'intégration. En définissant la propriété des classes ContextConfiguration , nous déclarons que Spring Boot doit utiliser la classe ApplicationCommandLineRunnerApp pour charger le contexte de l'application. En définissant l'initialiseur comme étant le ConfigFileApplicationContextInitializer , l'application charge ses propriétés .

Nous avons toujours besoin de @ExtendWith (SpringExtension.class) car cela intègre le Framework Spring TestContext dans le modèle de programmation Jupiter de JUnit 5.

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:

@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { ApplicationCommandLineRunnerApp.class }, initializers = ConfigFileApplicationContextInitializer.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 ConfigFileApplicationContextInitializer, when it is used alone, does not provide support for @Value(“${…​}”) injection. If we want to support it we have to configure a PropertySourcesPlaceholderConfigurer.

7. Conclusion

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

Comme toujours, le code est disponible sur sur GitHub.