使用@SpringBootTest注解来禁止CommandLineRunner运行
首先
当我使用Spring Boot的CommandLineRunner创建批处理应用程序时,当运行带有@SpringBootTest注解的测试时,会发生在测试操作之前运行CommandLineRunner的run方法的情况。本文将介绍原因解释和避免此问题的方法。
假设
翻译成中文如下:
酱料
以下是本次的源代码。
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