如果您或多或少有经验的Spring Boot用户,那么很幸运,在某些时候您可能需要遇到必须有条件地注入特定bean或配置的情况 。 它的机制是很好理解的 ,但有时这样的测试条件下(以及它们的组合)可能会导致混乱。 在这篇文章中,我们将讨论一些可行的方法(可以说是理智的)。
由于Spring Boot 1.5.x仍被广泛使用(不过,它将在8月向EOL迈进),因此我们会将其与Spring Boot 2.1.x一起包括在JUnit 4.x和JUnit 5.x中 。 我们将要介绍的技术同样适用于常规配置类和自动配置类。
我们将使用的示例与我们的自制日志记录有关。 让我们假设我们的Spring Boot应用程序需要一些名称为“ sample”的专用记录器bean。 但是,在某些情况下,必须禁用此记录器(或实际上使其成为noop),因此在这里,属性logging.enabled就像一个kill开关。 在此示例中,我们使用Slf4j和Logback ,但这并不是很重要。 下面的LoggingConfiguration片段反映了这个想法。
@Configuration public class LoggingConfiguration { @Configuration @ConditionalOnProperty (name = "logging.enabled" , matchIfMissing = true ) public static class Slf4jConfiguration { @Bean Logger logger() { return LoggerFactory.getLogger( "sample" ); } } @Bean @ConditionalOnMissingBean Logger logger() { return new NOPLoggerFactory().getLogger( "sample" ); } }
那么我们将如何测试呢? Spring Boot (通常是Spring Framework )一直提供出色的测试脚手架支持 。 @SpringBootTest和@TestPropertySource批注允许使用自定义属性快速引导应用程序上下文。 但是,有一个问题:它们是按测试类级别而不是每种测试方法应用的。 这当然是有道理的,但基本上需要您为每个条件组合创建一个测试类。
如果您仍然使用JUnit 4.x ,则可能会发现一个有用的技巧,该技巧利用了封闭的运行器(封闭的框架)。
@RunWith (Enclosed. class (Enclosed. ) public class LoggingConfigurationTest { @RunWith (SpringRunner. class ) @SpringBootTest public static class LoggerEnabledTest { @Autowired private Logger logger; @Test public void loggerShouldBeSlf4j() { assertThat(logger).isInstanceOf(ch.qos.logback.classic.Logger. class ); } } @RunWith (SpringRunner. class ) @SpringBootTest @TestPropertySource (properties = "logging.enabled=false" ) public static class LoggerDisabledTest { @Autowired private Logger logger; @Test public void loggerShouldBeNoop() { assertThat(logger).isSameAs(NOPLogger.NOP_LOGGER); } } }
您仍然可以按条件获得类,但至少它们都在同一嵌套中。 使用JUnit 5.x ,有些事情变得容易了,但没有达到人们期望的水平。 不幸的是, Spring Boot 1.5.x本身并不支持JUnit 5.x ,因此我们必须依靠spring-test-junit5社区模块提供的扩展。 这是pom.xml中的相关更改,请注意, spring-boot-starter-test依赖关系图中明确排除了junit 。
< dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >junit</ groupId > < artifactId >junit</ artifactId > </ exclusion > </ exclusions > </ dependency > < dependency > < groupId >com.github.sbrannen</ groupId > < artifactId >spring-test-junit5</ artifactId > < version >1.5.0</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < version >5.5.0</ version > < scope >test</ scope > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-engine</ artifactId > < version >5.5.0</ version > < scope >test</ scope > </ dependency >
除了使用@Nested批注(来自JUnit 5.x以支持将测试作为内部类)之外,测试用例本身没有太大区别。
public class LoggingConfigurationTest { @Nested @ExtendWith (SpringExtension. class ) @SpringBootTest @DisplayName ( "Logging is enabled, expecting Slf4j logger" ) public static class LoggerEnabledTest { @Autowired private Logger logger; @Test public void loggerShouldBeSlf4j() { assertThat(logger).isInstanceOf(ch.qos.logback.classic.Logger. class ); } } @Nested @ExtendWith (SpringExtension. class ) @SpringBootTest @TestPropertySource (properties = "logging.enabled=false" ) @DisplayName ( "Logging is disabled, expecting NOOP logger" ) public static class LoggerDisabledTest { @Autowired private Logger logger; @Test public void loggerShouldBeNoop() { assertThat(logger).isSameAs(NOPLogger.NOP_LOGGER); } } }
如果尝试使用Apache Maven和Maven Surefire插件从命令行运行测试,则可能会惊讶地发现在构建过程中没有执行任何测试。 问题是… 排除了所有嵌套类 …因此我们需要采用另一种解决方法 。
< plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-plugin</ artifactId > < version >2.22.2</ version > < configuration > < excludes > < exclude /> </ excludes > </ configuration > </ plugin >
这样,事情应该顺利进行。 但是关于传统的足够多的是, Spring Boot 2.1.x作为完整的游戏改变者来了。 上下文运行程序家族ApplicationContextRunner , ReactiveWebApplicationContextRunner和WebApplicationContextRunner提供了一种简单明了的方法,可以在每个测试方法级别上定制上下文,从而使测试执行异常Swift。
public class LoggingConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration(UserConfigurations.of(LoggingConfiguration. class )); @Test public void loggerShouldBeSlf4j() { runner .run(ctx -> assertThat(ctx.getBean(Logger. class )).isInstanceOf(Logger. class ) ); } @Test public void loggerShouldBeNoop() { runner .withPropertyValues( "logging.enabled=false" ) .run(ctx -> assertThat(ctx.getBean(Logger. class )).isSameAs(NOPLogger.NOP_LOGGER) ); } }
看起来真的很棒。 Spring Boot 2.1.x对JUnit 5.x的支持要好得多,并且在即将到来的2.2版本中 发布时, JUnit 5.x将是默认引擎 (不用担心,仍将支持旧的JUnit 4.x )。 到目前为止,切换到JUnit 5.x需要在依赖项方面进行一些工作。
< dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >junit</ groupId > < artifactId >junit</ artifactId > </ exclusion > </ exclusions > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < scope >test</ scope > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-engine</ artifactId > < scope >test</ scope > </ dependency >
作为附加步骤,您可能需要使用最新的Maven Surefire插件 ( 2.22.0或更高版本)以及现成的JUnit 5.x支持。 下面的代码段说明了这一点。
< plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-surefire-plugin</ artifactId > < version >2.22.2</ version > </ plugin >
我们使用的示例配置非常幼稚,许多实际应用程序最终将具有由许多条件构建而成的非常复杂的上下文。 上下文赛跑者带来的灵活性和巨大的机会, Spring Boot 2.x测试脚手架的宝贵补充,只是节省大量资金,请紧记它们。
完整的项目资源可在Github上找到 。
翻译自: https://www.javacodegeeks.com/2019/08/testing-spring-boot-conditionals-sane-way.html