背景

最近搭建项目的测试框架,发现项目里用到了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方法

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐