1. 개요

이 예제에서는 Spring Boot 통합 테스트 중에 ApplicationRunner 또는 CommandLineRunner 유형의 Bean이 실행 되지 않도록 방지하는 방법을 보여줍니다 .

2. 예제 애플리케이션

예제 애플리케이션은 명령 줄 실행기, 애플리케이션 실행기 및 태스크 서비스 빈으로 구성됩니다.

명령 줄 실행기 는 응용 프로그램 시작시 작업을 수행하기 위해 작업 서비스의 실행 메서드를 호출합니다 .

@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. 예상되는 동작 테스트

ApplicationRunnerTaskExecutorCommandLineTaskExecutor의 Spring 부팅로드 응용 프로그램 컨텍스트 후 실행됩니다.

간단한 테스트를 통해이를 확인할 수 있습니다.

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

보시다시피, 우리는 Mockito 스파이ApplicationRunnerTaskExecutorCommandLineTaskExecutor  빈 에 적용하기 위해 SpyBean 어노테이션을 사용하고 있습니다. 그렇게함으로써 이러한 각 빈 실행 메소드가 한 번 호출 되었는지 확인할 수 있습니다 .

다음 섹션에서는 Spring Boot 통합 테스트 중에 이러한 기본 동작을 방지하는 다양한 방법과 기술을 살펴 보겠습니다.

4. 스프링 프로파일을 통한 예방

이 두 가지가 실행되는 것을 막을 수있는 한 가지 방법은 @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 어노테이션을 통한 예방

또는 속성별로 연결을 구성한 다음 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
}

우리가 볼 때, ApplicationRunnerTaskExecutorCommandLineTaskExecutor은 기본적으로 활성화되어, 우리가 다음과 같은 속성을 설정하면 우리는 그들을 해제 할 수 있습니다 거짓을 :

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

따라서 테스트에서 이러한 속성을 false로 설정 하고 ApplicationRunnerTaskExecutor 또는 CommandLineTaskExecutor Bean이 애플리케이션 컨텍스트에로드 되지 않습니다 .

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

이제 위의 기술이 목표를 달성하는 데 도움이되지만 모든 Spring Bean이 올바르게로드되고 연결되었는지 테스트하려는 경우가 있습니다.

예를 들어, TaskService  빈이 CommandLineTaskExecutor에 올바르게 주입되었는지 테스트하고 싶을 수 있지만 여전히 테스트 중에 실행 메서드가 실행되는 것을 원하지 않습니다 . 그래서 우리가 그것을 달성 할 수있는 방법을 설명하는 마지막 섹션을 봅시다.

6. 전체 컨테이너를 부트 스트랩하지 않음으로써 예방

여기에서는 전체 애플리케이션 컨테이너를 부트 스트랩하지 않음 으로써 CommandLineTaskExecutorApplicationRunnerTaskExecutor Bean이 실행 되지 않도록 방지 할 수있는 방법을 설명합니다 .

이전 섹션에서는 @SpringBootTest 어노테이션 을 사용했으며 이로 인해 통합 테스트 중에 전체 컨테이너가 부트 스트랩되었습니다. @SpringBootTest 에는 이 마지막 솔루션과 관련된 두 개의 메타 어노테이션포함되어 있습니다. 

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

글쎄, 테스트 중에 전체 컨테이너를 부트 스트랩 할 필요가 없다면 @BootstrapWith 를 사용하고 싶지 않습니다 .

대신  @ContextConfiguration으로 바꿀 수 있습니다 .

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

 @ContextConfiguration을 사용 하여 통합 테스트를 위해 애플리케이션 컨텍스트를로드하고 구성하는 방법을 결정합니다. ContextConfiguration 클래스 속성 을 설정하여 Spring Boot가 ApplicationCommandLineRunnerApp 클래스를 사용하여 애플리케이션 컨텍스트를로드하도록 선언합니다 . 이니셜 라이저를 ConfigDataApplicationContextInitializer 로 정의하여 응용 프로그램은 해당 속성을로드합니다 .

Spring TestContext Framework를 JUnit 5의 Jupiter 프로그래밍 모델에 통합하기 때문에 여전히 @ExtendWith (SpringExtension.class) 가 필요합니다 . 

위의 결과로 Spring Boot 애플리케이션 컨텍스트는 CommandLineTaskExecutor 또는 ApplicationRunnerTaskExecutor 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());
    }
}

또한 ConfigDataApplicationContextInitializer 가 단독으로 사용되는 경우 @Value (“$ {…}”) 삽입을 지원하지 않는다는 점을 명심해야 합니다 . 이를 지원하려면 PropertySourcesPlaceholderConfigurer 를 구성해야합니다 .

7. 결론

이 기사에서는 Spring Boot 통합 테스트 중에 ApplicationRunnerCommandLineRunner Bean 의 실행을 방지하는 다양한 방법을 보여주었습니다 .

항상 그렇듯이 코드는 GitHub에서 사용할 수 있습니다 .