Impedisci l'esecuzione dei bean ApplicationRunner o CommandLineRunner durante i test Junit

1. Panoramica

In questo tutorial, mostreremo come possiamo impedire l' esecuzione di bean di tipo ApplicationRunner o CommandLineRunner durante i test di integrazione di Spring Boot.

2. Applicazione di esempio

La nostra applicazione di esempio è costituita da un runner della riga di comando, un runner dell'applicazione e un bean del servizio attività.

Il runner della riga di comando chiama il metodo di esecuzione del servizio attività per eseguire un'attività all'avvio dell'applicazione:

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

Allo stesso modo, il runner dell'applicazione interagisce con il servizio attività per eseguire un'altra attività:

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

Infine, il servizio attività è responsabile dell'esecuzione delle attività del suo cliente:

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

E abbiamo anche una classe di applicazioni Spring Boot che fa funzionare tutto:

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

3. Testare il comportamento previsto

L'ApplicationRunnerTaskExecutor ed il CommandLineTaskExecutor fuga dopo Primavera avvio carica il contesto dell'applicazione.

Possiamo verificarlo con un semplice 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()); } }

Come si vede, stiamo usando l' annotazione SpyBean per applicare spie Mockito ai bean ApplicationRunnerTaskExecutor e CommandLineTaskExecutor . In questo modo, possiamo verificare che il metodo di esecuzione di ciascuno di questi bean sia stato chiamato una volta.

Nelle prossime sezioni, vedremo vari modi e tecniche per prevenire questo comportamento predefinito durante i nostri test di integrazione Spring Boot.

4. Prevenzione tramite profili a molla

Un modo per impedire l'esecuzione di questi due è annotandoli con @Profile :

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

Dopo le modifiche di cui sopra, procediamo con il nostro test di integrazione:

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

Come si vede, abbiamo annotato la classe di test sopra con l' annotazione @ActiveProfiles ("test") , il che significa che non collegherà quelli annotati con @Profile ("! Test") . Di conseguenza, né il bean CommandLineTaskExecutor né il bean ApplicationRunnerTaskExecutor vengono caricati.

5. Prevenzione tramite l' annotazione ConditionalOnProperty

Oppure, possiamo configurare il loro cablaggio in base alla proprietà e quindi utilizzare l' annotazione 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 }

Come si vede, l'ApplicationRunnerTaskExecutor e la CommandLineTaskExecutor sono abilitate per default, e siamo in grado di disattivare loro se abbiamo impostato le seguenti proprietà falsa :

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

Quindi, nel nostro test, abbiamo impostato queste proprietà su false e né i bean ApplicationRunnerTaskExecutor né i bean CommandLineTaskExecutor vengono caricati nel contesto dell'applicazione :

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

Ora, sebbene le tecniche di cui sopra ci aiutino a raggiungere il nostro obiettivo, ci sono casi in cui vogliamo testare che tutti i bean Spring siano caricati e cablati correttamente.

Per esempio, si può decidere di test che il TaskService chicco viene iniettato correttamente al CommandLineTaskExecutor, ma ancora non vuole la sua corsa il metodo da eseguire durante la nostra prova. Quindi, vediamo l'ultima sezione che spiega come possiamo ottenerlo.

6. Prevenzione non eseguendo il bootstrap dell'intero contenitore

Qui, descriveremo come possiamo impedire l' esecuzione dei bean CommandLineTaskExecutor e ApplicationRunnerTaskExecutor non eseguendo il bootstrap dell'intero contenitore dell'applicazione.

Nelle sezioni precedenti, abbiamo utilizzato l' annotazione @SpringBootTest e questo ha comportato il bootstrap dell'intero contenitore durante i nostri test di integrazione. @SpringBootTest include due meta-annotazioni rilevanti per quest'ultima soluzione:

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

Bene, se non è necessario eseguire il bootstrap dell'intero contenitore durante il nostro test, non utilizzare @BootstrapWith .

Invece, possiamo sostituirlo con @ContextConfiguration :

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

Con @ContextConfiguration, determiniamo come caricare e configurare il contesto dell'applicazione per i test di integrazione. Impostando la proprietà ContextConfiguration delle classi , si dichiara che Spring Boot deve utilizzare la classe ApplicationCommandLineRunnerApp per caricare il contesto dell'applicazione. Definendo l'inizializzatore come ConfigFileApplicationContextInitializer , l'applicazione carica le sue proprietà .

Abbiamo ancora bisogno di @ExtendWith (SpringExtension.class) poiché integra lo Spring TestContext Framework nel modello di programmazione Jupiter di 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.

Come sempre, il codice è disponibile su GitHub.