让我的Spring测试切片扫描单个类而不是整个包

5
我被要求为一个现有的SpringBoot项目创建集成测试,但该项目的组织结构不如我所愿。例如,有一个包含所有服务关联的存储库的包。当我尝试创建@WebMvcTest测试片段时,这对我造成了问题,因为当我使用@ComponentScan、@EnableJpaRepositories、@EntityScan来读取我的目标类时,它会扫描所有共享同一包的其他不必要的类。
由于更改项目结构不是我能够自主决定的,我的问题是是否可能让我的测试扫描选择特定的类并忽略同一包中的所有其他类?
感谢您的关注。

https://dev59.com/EVoU5IYBdhLWcg3wK0pX - Allenaz
2个回答

2
我终于成功实现了所有必要的过滤,这要感谢Josef的回答以及以下链接的帮助: 组件和服务可以配置为产生过滤器,因此我们可以同时指定目标服务和控制器并排除其他一切内容。
 @ComponentScan(
        basePackageClasses = {
                MyTargetService.class,
                MyTargetController.class
        },
        useDefaultFilters = false,
        includeFilters = {
                @ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = MyTargetService.class),
                @ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = MyTargetController.class)

        }
)

仓库。 这对于仓库来说不太可能起作用,但幸运的是@EnableJpaRepositories支持相同类型的过滤器:

  @EnableJpaRepositories(
       basePackageClasses = {
            MyTargetRepository.class
       },
       includeFilters = {
            @ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = MyTargetRepository.class)
       }
  )

实体类. 这部分比较棘手,因为 @EntityScan 不支持这些过滤器。虽然实体类没有引用 Spring bean,但我更喜欢仅加载测试所需的实体类。我没有找到任何支持过滤的实体类注解,但我们可以在 EntityManagerFactory 中使用 PersistenceUnitPostProcessor 以编程方式对它们进行过滤。以下是我的完整解决方案:

   //add also the filtered @ComponentScan and @EnableJpaRepositories annotations here
   @Configuration
   public class MyConfig {

    //here we specify the packages of our target entities
    private static String[] MODEL_PACKAGES = {
            "com.full.path.to.entity.package1",
            "com.full.path.to.entity.package2"
    };

    //here we specify our target entities
    private static Set<String> TARGET_ENTITIES = new HashSet<>(Arrays.asList(
            "com.full.path.to.entity.package1.MyTargetEntity1",
            "com.full.path.to.entity.package2.MyTargetEntity2"
    ));

    @Bean
    public DataSource getDataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.H2).build();
    }

    @Bean
    public EntityManagerFactory entityManagerFactory() {

        ReflectionsPersistenceUnitPostProcessor reflectionsPersistenceUnitPostProcessor = new ReflectionsPersistenceUnitPostProcessor();

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(MODEL_PACKAGES);
        factory.setDataSource(getDataSource());
        factory.setPersistenceUnitPostProcessors(reflectionsPersistenceUnitPostProcessor);
        factory.afterPropertiesSet();

        return factory.getObject();
    }


    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory());
        return txManager;
    }

    public class ReflectionsPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {

        @Override
        public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {

            Reflections r = new Reflections("", new TypeAnnotationsScanner());
            Set<Class<?>> entityClasses = r.getTypesAnnotatedWith(Entity.class, true);
            Set<Class<?>> mappedSuperClasses = r.getTypesAnnotatedWith(MappedSuperclass.class, true);

            pui.getManagedClassNames().clear(); //here we remove all entities

            //here we add only the ones we are targeting
            for (Class<?> clzz : mappedSuperClasses) {
                if (TARGET_ENTITIES.contains(clzz.getName())) {
                    pui.addManagedClassName(clzz.getName());
                }
            }
            for (Class<?> clzz : entityClasses) {
                if (TARGET_ENTITIES.contains(clzz.getName())) {
                    pui.addManagedClassName(clzz.getName());
                }
            }

        }

    }


}

1

ComponentScan可以与slices一起使用。实际上,它是在SpringBootApplication注解本身上进行配置的。使测试slices与ComponentScan配合工作的部分是TypeExcludeFilter

@ComponentScan(
    basePackages = "com.mycompany.someotherpackage",
    excludeFilters = {
      @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    })

谢谢!如果我有一个包含多个实体的包,但我只对其中一个感兴趣,该怎么办? - João Matos
@JoãoMatos 很抱歉,我没有相关经验。通常实体之间会相互引用,因此必须加载更多实体。但同时,除了在数据库中生成表所花费的时间外,加载它们所有并不会产生额外的开销。它们不会引用Spring bean,因此未来的更改不应该破坏您现有的测试。 - Josef Cech

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接