使用@SpringBootTest注解来禁止CommandLineRunner运行

首先

当我使用Spring Boot的CommandLineRunner创建批处理应用程序时,当运行带有@SpringBootTest注解的测试时,会发生在测试操作之前运行CommandLineRunner的run方法的情况。本文将介绍原因解释和避免此问题的方法。

假设

ライブラリバージョンSpring Boot2.1.6.RELEASE

翻译成中文如下:
酱料

以下是本次的源代码。
https://github.com/kawakawaryuryu/command-line-runner-sample

我实施了什么样的方案

我做了一个类似下面的简略实现。

@SpringBootApplication
public class SampleApplication {
    public static void main(String... args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}
// CommandLineRunner実装クラス
@Component
@Slf4j
@PropertySource("config.properties")
public class JobLauncher implements CommandLineRunner {

    private final String hogeValue;

    public JobLauncher(
            @Value("${hoge.value}") String hogeValue) {
        this.hogeValue = hogeValue;
    }

    @Override
    public void run(String... args) {
        log.info("app started");
    }

    public String hoge() {
        log.info(hogeValue);
        return hogeValue;
    }
}
hoge.value=hoge

这次要测试的目标方法hoge本来应该做更加完整的处理,但在这里故意简化了。

接下来是测试类的内容。

@RunWith(SpringRunner.class)
@SpringBootTest
public class JobLauncherTest {

    @Autowired
    private JobLauncher jobLauncher;

    @Test
    public void testHoge() {
        String hoge = jobLauncher.hoge();
        assertThat(hoge).isEqualTo("hoge");
    }
}

当执行这个命令时,测试执行时的Spring启动日志(部分)将会显示如下。

...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

...
2019-07-27 22:56:54.479  INFO 49823 --- [           main] c.k.c.JobLauncherTest                    : Started JobLauncherTest in 1.48 seconds (JVM running for 3.023)
#↓hogeメソッドの前にCommandLineRunnerのrunメソッドが走っている↓
2019-07-27 22:56:54.481  INFO 49823 --- [           main] c.k.commandlinerunnersample.JobLauncher  : app started
2019-07-27 22:56:54.892  INFO 49823 --- [           main] c.k.commandlinerunnersample.JobLauncher  : hoge

...

你懂吗?是的,在JobLauncherTest类的测试运行之前,CommandLineRunner的run方法就已经被执行了。
这次只是输出日志处理,所以测试会成功,但是如果JobLauncher依赖于很多类,测试将变得困难并且会感到困扰。

导致这个结果的事物或事件。

当我做了一些调查后,我在官方文档中找到了关于CommandLineRunner的描述。

如果您需要在SpringApplication启动后运行某些特定代码,您可以实现ApplicationRunner或CommandLineRunner接口。这两个接口的工作方式相同,并且都提供一个单独的run方法,在SpringApplication.run(…​)完成之前调用。

换句话说,CommandLineRunner会在SpringApplication.run完成之前被调用。
另外,@SpringBootTest是一个注解,可以使用Spring Boot功能进行测试,但是它通过SpringApplication生成ApplicationContext。

Spring Boot提供了@SpringBootTest注解,当你需要Spring Boot功能时,它可以作为标准spring-test @ContextConfiguration注解的替代方案。该注解通过创建由SpringApplication在你的测试中使用的ApplicationContext来实现。

根据这些理由,我认为他们可能会在考试前被召集。

解决方案 (jiě jué cè)

根据我的调查发现,有几种解决方案可供选择,但在这里我将写下我推荐使用的方法,即使用@ContextConfiguration注解。
我成功地实施了以下的解决方案。

@RunWith(SpringRunner.class)
@ContextConfiguration(
        classes = TestConfiguration.class,
        initializers = ConfigFileApplicationContextInitializer.class)
public class JobLauncherTest {

    @Autowired
    private JobLauncher jobLauncher;

    @Test
    public void testHoge() {
        String hoge = jobLauncher.hoge();
        assertThat(hoge).isEqualTo("hoge");
    }
}
@ComponentScan
@Configuration
public class TestConfiguration {
}

说明

@上下文配置

@ContextConfiguration注解是一个用于实现使用Spring功能进行测试的注解。它与@SpringBootTest的区别在于能否使用Spring Boot功能进行测试。

正如先前提到的,@SpringBootTest通过SpringApplication生成ApplicationContext,而@ContextConfiguration则通过指定的配置生成ApplicationContext,因此完全不使用SpringApplication。

因此,不必担心是否会调用CommandLineRunner的run方法。

    • Testing

 

    ContextConfiguration (Spring Framework 5.1.8.RELEASE API)

@ContextConfiguration注解的initializers字段。

在@ContextConfiguration的initializers参数中,可以指定特定的ApplicationContextInitializer。
ApplicationContextInitializer是在ApplicationContext初始化时可以插入特定处理的对象。
在这里我们插入了名为ConfigFileApplicationContextInitializer的初始化器。

    ApplicationContextInitializer (Spring Framework 5.1.8.RELEASE API)

配置文件应用程序上下文初始化器

ConfigFileApplicationContextInitializer是一个初始化器,它可以从application.properties或application.yml文件中读取值并存储到Environment中。通过这个初始化器,我们可以利用Spring Boot的外部配置文件读取功能来进行测试,而不仅限于@SpringBootTest注解。本次测试利用了这个初始化器,实现了从application.properties中获取配置值的功能。

    https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-configfileapplicationcontextinitializer-test-utility

@ComponentScan注解中的TestConfiguration

@SpringBootTest在带有@SpringBootTest注解的测试类的包中向上查找,并寻找包含@SpringBootApplication或@SpringBootConfiguration的配置,然后根据该配置生成应用程序上下文。由于@SpringBootApplication包含@ComponentScan,因此在使用@SpringBootTest时,基本上无需指定任何配置即可成功扫描和注册Bean。

相反,@ContextConfiguration没有这样的功能,因此需要手动扫描和注册Bean。因此,本例中我们在配置上添加了@ComponentScan,并在@ContextConfiguration中指定它,以进行Bean的扫描和注册。

神秘之处

当阅读Spring Boot官方文档中的ConfigFileApplicationContextInitializer页面时,还发现了以下的描述。

仅仅使用ConfigFileApplicationContextInitializer无法支持@Value(“${…}”)注入。它的唯一任务是确保将application.properties文件加载到Spring的环境中。要支持@Value注解,您需要额外配置一个PropertySourcesPlaceholderConfigurer或使用@SpringBootTest注解,它可以自动配置一个。

换句话说,仅仅使用ConfigFileApplicationContextInitializer是无法实现@Value注入的。如果想要进行注入,需要配置PropertySourcesPlaceholderConfigurer,或者使用可以自动配置的@SpringBootTest。但是,我自己尝试使用@ContextConfiguration和ConfigFileApplicationContextInitializer,仅仅这两个就可以实现@Value注入。我仍然不明白为什么只有这一点可以成功注入。

也许从Spring 4.3开始,似乎不需要明确指定PropertySourcesPlaceholderConfigurer,这是否相关?

我希望将来继续进行调查。

其他參考

    • SpringApplication (Spring Boot Docs 2.1.6.RELEASE API)

 

    • SpringBootTest (Spring Boot Docs 2.1.6.RELEASE API)

 

    • Spring Bootでコマンドラインアプリを作る時の注意点 – Qiita

 

    • SpringBootでのJUnitでCommandLineRunnerが動くことの回避策 – Qiita

 

    spring – Prevent Application / CommandLineRunner classes from executing during JUnit testing – Stack Overflow
广告
将在 10 秒后关闭
bannerAds