【测试相关】如何测试Spring Boot自定义的AutoCon
1. 创建自己的Auto-configuration
官网文档(Creating Your Own Auto-configuration)
:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration
更多的Auto-configuration,可以参考spring-cloud-sleuth-autoconfigure
项目,它是sleuth的auto-configure模块:
https://github.com/spring-cloud/spring-cloud-sleuth/tree/3.1.x/spring-cloud-sleuth-autoconfigure
比如针对@Shcheduled的trace自动装配类:TraceSchedulingAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint")
@ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracer.class)
@EnableConfigurationProperties(SleuthSchedulingProperties.class)
@AutoConfigureAfter(BraveAutoConfiguration.class)
public class TraceSchedulingAutoConfiguration {
@Bean
TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, SleuthSchedulingProperties sleuthSchedulingProperties) {
String skipPatternString = sleuthSchedulingProperties.getSkipPattern();
Pattern skipPattern = skipPatternString != null ? Pattern.compile(skipPatternString) : null;
return new TraceSchedulingAspect(tracer, skipPattern);
}
}
也可以自已写一个,当项目classpath中有javax.servlet.Filter
这个类时,并且com.test.user != false
时,就会自动创建bean为userStarterService:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Filter.class)
@ConditionalOnProperty(value = "com.test.user", matchIfMissing = true)
public class UserAutoConfiguration {
@Bean
public UserStarterService userStarterService() {
return new UserStarterService();
}
}
2. 如何通过Unit Test测试我们的Auto-Configure类
上述我们自己的UserAutoConfiguration
类,在现实中可以创建一个空的项目A,再引入这个类所在的starter包,那么运行项目A后会发现没有创建userStarterService bean。或是在项目A中application.yaml,将com.test.user置为false,同样的也不会创建userStarterService bean。
想要测试创建userStarterService bean的case,那么可以创建创目B,再引入UserAutoConfiguration所在的starter包,将引入spring-boot-starter-web
(因为这个包中有Filter类),那么在启动项目B的时候,就会自动创建userStarterService bean。
上述是后期在集成中的case。但在UserAutoConfiguration所在的starter项目中,应该要有自己的关于这个类的单元测试。即如何测试Spring Boot自定义的AutoConfiguration
类?
官方文档(Testing your Auto-configuration)
:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.testing
即使用ApplicationContextRunner
类进行测试。这个类位于spring-boot-test
包中。
针对主述的UserAutoConfiguration,我们可以写如下的测试类:
- 首先需要创建【
ApplicationContextRunner
】类,并且需要告诉它总是需要加载UserAutoConfiguration.class
(这里可以传入多个class)。 - 测试方法中的
Assertions
不是JUnit5中的那个类,而是AspectJ中的,(因为JUnit5中的没有assertThat方法)。具体的类为:org.assertj.core.api.Assertions
。 - 因为starter本身肯定是需要Filter.class类的(因为在UserAutoConfiguration有用到),那么怎么测试classpath没有Filter.class的情况呢?可以使用【
ApplicationContextRunner#withClassLoader
】重写Classpath:
FilteredClassLoader
会将传入它的类在现有的classpath中移除掉。即模拟项目中没有Filter.class类的时候,那么@ConditionalOnClass(Filter.class)
不生效,即不会创建userStarterService bean。所以在verify的时候,用的是方法doesNotHaveBean
。 - 同样的,ApplicationContextRunner可以模拟property value,例如使用【
withPropertyValues("com.test.user=false")
】,可以轻松的测试@ConditionalOnProperty(value = "com.test.user", matchIfMissing = true)
不匹配的情况。
public class UserAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(UserAutoConfiguration.class));
@Test
public void should_create_bean() {
contextRunner.run(context -> Assertions.assertThat(context).hasSingleBean(UserStarterService.class));
}
@Test
public void should_not_create_bean_as_no_filter_class() {
contextRunner.withClassLoader(new FilteredClassLoader(Filter.class))
.run(context -> Assertions.assertThat(context).doesNotHaveBean(UserStarterService.class));
}
@Test
public void should_not_create_bean_as_property_false() {
contextRunner.withPropertyValues("com.test.user=false")
.run(context -> Assertions.assertThat(context).doesNotHaveBean(UserStarterService.class));
}
}
3. 更多的sample
关于#1中引用的Spring Sleuth的TraceSchedulingAutoConfiguration
类,它的测试类源码可以看 github。
总体上就是我们上述介绍的方式。sleuth中还有更多的关于Auto-Configure的单元测试,可以在github中看。
import org.aspectj.lang.ProceedingJoinPoint;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAspect;
class TraceSchedulingAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(
AutoConfigurations.of(TraceNoOpAutoConfiguration.class, TraceSchedulingAutoConfiguration.class));
@Test
void shoud_create_TraceSchedulingAspect() {
this.contextRunner.run(context -> Assertions.assertThat(context).hasSingleBean(TraceSchedulingAspect.class));
}
@Test
void shoud_not_create_TraceSchedulingAspect_without_aspectJ() {
this.contextRunner.withClassLoader(new FilteredClassLoader(ProceedingJoinPoint.class))
.run(context -> Assertions.assertThat(context).doesNotHaveBean(TraceSchedulingAspect.class));
}
}