Cegah Bean ApplicationRunner atau CommandLineRunner daripada Dilaksanakan Semasa Ujian Junit

1. Gambaran keseluruhan

Dalam tutorial ini, kami akan menunjukkan bagaimana kami dapat mencegah kacang jenis ApplicationRunner atau CommandLineRunner daripada berjalan semasa ujian integrasi Spring Boot.

2. Contoh Permohonan

Aplikasi contoh kami terdiri daripada pelari baris perintah, pelari aplikasi, dan kacang servis tugas.

Pelari baris perintah, memanggil kaedah pelaksanaan perkhidmatan tugas , untuk melakukan tugas pada permulaan aplikasi:

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

Dengan cara yang sama, pelari aplikasi berinteraksi dengan perkhidmatan tugas untuk melakukan tugas lain:

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

Akhirnya, perkhidmatan tugas bertanggungjawab untuk melaksanakan tugas pelanggannya:

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

Dan, kami juga mempunyai kelas aplikasi Spring Boot yang menjadikan semuanya berfungsi:

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

3. Menguji tingkah laku yang diharapkan

The ApplicationRunnerTaskExecutor dan CommandLineTaskExecutor jangka selepas beban Spring Boot konteks permohonan.

Kami dapat mengesahkannya dengan ujian mudah:

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

Seperti yang kita lihat, kita menggunakan anotasi SpyBean untuk menerapkan mata-mata Mockito ke kacang ApplicationRunnerTaskExecutor dan CommandLineTaskExecutor . Dengan berbuat demikian, kita dapat mengesahkan bahawa kaedah larian setiap biji ini disebut sekali.

Pada bahagian seterusnya, kita akan melihat pelbagai cara dan teknik untuk mencegah tingkah laku lalai ini semasa ujian integrasi Spring Boot kami.

4. Pencegahan melalui Profil Spring

Salah satu cara untuk mengelakkan kedua-duanya berjalan adalah dengan memberi penjelasan kepada mereka dengan @Profile :

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

Selepas perubahan di atas, kami meneruskan ujian integrasi kami:

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

Seperti yang kita lihat, kita memberi penjelasan pada kelas ujian di atas dengan anotasi @ActiveProfiles ("test") , yang bermaksud tidak akan memasukkan yang dijelaskan dengan @Profile ("! Test") . Akibatnya, kacang CommandLineTaskExecutor atau kacang ApplicationRunnerTaskExecutor dimuat sama sekali.

5. Pencegahan melalui ConditionalOnProperty Anotasi

Atau, kita boleh mengkonfigurasi pendawaian mereka mengikut harta tanah dan kemudian menggunakan anotasi 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 }

Seperti yang kita lihat, yang ApplicationRunnerTaskExecutor dan CommandLineTaskExecutor diaktifkan secara piawai, dan kita boleh mematikan mereka jika kita menetapkan sifat-sifat berikut untuk palsu :

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

Oleh itu, dalam ujian kami, kami menetapkan sifat ini menjadi palsu dan kacang ApplicationRunnerTaskExecutor atau CommandLineTaskExecutor dimuat ke konteks aplikasi :

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

Sekarang, walaupun teknik di atas membantu kita mencapai tujuan kita, ada kes di mana kita ingin menguji bahawa semua kacang Spring dimuat dan dipasang dengan betul.

Sebagai contoh, kami mungkin ingin menguji bahawa kacang TaskService disuntikkan dengan betul ke CommandLineTaskExecutor, tetapi kami masih tidak mahu kaedah lariannya dijalankan selama ujian kami. Oleh itu, mari kita lihat bahagian terakhir yang menerangkan bagaimana kita dapat mencapainya.

6. Pencegahan dengan Tidak Menarik Seluruh Bekas

Di sini, kami akan menerangkan bagaimana kami dapat mencegah kacang CommandLineTaskExecutor dan ApplicationRunnerTaskExecutor daripada dilaksanakan dengan tidak bootstrap keseluruhan wadah aplikasi.

Pada bahagian sebelumnya, kami menggunakan anotasi @SpringBootTest dan ini menyebabkan keseluruhan kontena diikat semasa ujian integrasi kami. @SpringBootTest merangkumi dua meta-anotasi yang berkaitan dengan penyelesaian terakhir ini:

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

Jika tidak perlu bootstrap seluruh bekas semasa ujian kami, maka jangan mahu menggunakan @BootstrapWith .

Sebagai gantinya, kita boleh menggantinya dengan @ContextConfiguration :

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

Dengan @ContextConfiguration, kami menentukan cara memuat dan mengkonfigurasi konteks aplikasi untuk ujian integrasi. Dengan menetapkan harta kelas ContextConfiguration , kami menyatakan bahawa Spring Boot harus menggunakan kelas ApplicationCommandLineRunnerApp untuk memuat konteks aplikasi. Dengan menentukan inisialisasi menjadi ConfigFileApplicationContextInitializer , aplikasi memuatkan sifatnya .

Kami masih memerlukan @ExtendWith (SpringExtension.class) kerana itu mengintegrasikan Spring TestContext Framework ke dalam model pengaturcaraan Jupiter 5 JUnit.

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.

Seperti biasa, kodnya tersedia di GitHub.