springboottest mock所有FeignClient
背景
最近搭建项目的测试框架,发现项目里用到了openfeign,在测试用例中,FeignClient是肯定调不通的,且不应该实际去调用外部系统,这不符合测试用例的不依赖外部数据的规则,那么就需要对FeignClient进行mock。
本来想用到了哪些FeignClient就mock哪个,最后发现FeignClient实在太多了,手动一个一个加很麻烦,并且以后有新的FeignClient还需要手动加。
那么,就需要自动扫描所有@FeignClient并且注入mock bean。
思路
1.将原本openfeign框架的自动注入FeignClient关掉,不然会和我们自己注入的mock bean冲突
2.扫描@FeignClient接口,注入mock bean
3.支持mock bean配置全局mock方法
实现
关闭openfeign框架的自动注入FeignClient
openfeign通过@EnableFeignClients去注入FeignClient
@EnableFeignClients在项目里是加在启动类上的
那么就需要在测试用例中排除掉启动类
1.创建测试启动类
测试启动类上的注解复制主启动类上的,但是不复制@EnableFeignClients
2.排除主启动类
写一个联合注解IntegrationTest,指定测试配置为TestApplication,并且通过BeanExcludeFilter排除主启动类
这时候启动springboottest的时候,就不会扫描FeignClient生成bean,依赖这些bean的地方开始报错,找不到依赖bean
扫描FeignClient生成mock bean

参考@EnableFeignClients定义自己的注解
将注解添加到TestApplication上,basePackages和主启动类配置一样
public class MockFeignClientRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = this.getScanner(registry);
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = this.getBasePackages(metadata);
scanner.scan(basePackages.toArray(new String[0]));
}
public Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(MockEnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet();
for(String pkg : (String[])attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for(String pkg : (String[])attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for(Class<?> clazz : (Class[])attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
protected ClassPathBeanDefinitionScanner getScanner(BeanDefinitionRegistry registry) {
return new MockBeanClassPathBeanDefinitionScanner(registry);
}
static class MockBeanClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public MockBeanClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
setBeanNameGenerator(new BeanNameGenerator() {
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
return beanDefinition.getBeanClassName();
}
});
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
return isCandidate;
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
this.processBeanDefinitions(beanDefinitions);
return beanDefinitions;
}
@Override
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
beanName = beanDefinition.getBeanClassName();
super.postProcessBeanDefinition(beanDefinition, beanName);
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for (BeanDefinitionHolder holder : beanDefinitions) {
AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition();
definition.setPrimary(true);
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(MockBeanFactoryBean.class);
definition.setAttribute("beanInterface", definition.getBeanClassName());
}
}
}
}
扫描所有@FeignClient,注册bean,类型为MockBeanFactoryBean
支持对mock bean提供默认实现
某些FeignClient可能经常用到,且某些方法返回值固定,可以直接提供全局的默认实现,不需要每个测试用例里自己去mock
自此,就实现了动态为FeignClient注入mock bean,并且支持提供全局的mock方法
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)