禁用在SpringBootTest中使用spring.config.import激活的配置服务客户端



我有一个裸露的Spring Boot应用程序

@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}

它连接到具有以下application.yml的Spring Cloud配置服务器

spring:
application:
name: client
config:
import: configserver:http://localhost:8888

当配置服务器正在运行时,应用程序运行良好,而当服务器未运行时,则会出现预期的故障。

我现在想用@SpringBootTest为应用程序编写一个集成测试,该应用程序不依赖于正在运行的配置服务器,甚至不尝试连接到它

在配置服务器关闭的情况下,裸测试

@SpringBootTest
class ClientApplicationTests {
@Test
void contextLoads() {
}
}

java.net.ConnectException: Connection refused失败,这是预期的。

当我尝试使用禁用配置客户端时

@SpringBootTest(properties = "spring.cloud.config.enabled=false")

测试没有尝试连接到服务器,但失败

Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
...
Caused by: java.lang.IllegalStateException: Unable to load config data from 'configserver:http://localhost:8888'
at org.springframework.boot.context.config.StandardConfigDataLocationResolver.getReferences(StandardConfigDataLocationResolver.java:141)
...
Caused by: java.lang.IllegalStateException: File extension is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/' or File.separator
at org.springframework.boot.context.config.StandardConfigDataLocationResolver.getReferencesForFile(StandardConfigDataLocationResolver.java:229)

问题是,Spring Boot 2.4中新引入的属性spring.config.import的工作方式与所有其他属性不同。在我看来,只有价值可以添加到其中,但永远不会删除。

有没有一种方法可以覆盖SpringBootTest中的spring.config.import?有没有办法只抑制到配置服务器的连接?

仅用于测试的解决方案

configserver.import = configserver:https://localhost:8888
spring.config.import = ${configserver.import}

通过这种方式,您可以在测试中将属性configserver.import设置为空字符串:

configserver.import = 

现在为空的spring.config.import属性将不再被考虑。这甚至可以在prod环境中使用-DJVM启动参数重写。

在测试场景中,这必须与spring.cloud.config.enabled=false配对,因为spring cloud配置会检查至少一个以configserver:前缀开头的spring.config.import值,如果找不到匹配的属性,则不允许您启动。

错误

Caused by: java.lang.IllegalStateException: File extension is not known to any PropertySourceLoader

是由通过spring.cloud.config.enabled=false禁用spring-cloud-config引起的,因为这也删除了处理所有前缀为configserver:的PropertySourceLoader bean。

产品环境URL替换解决方案(还包括测试修复程序)

这里有另一个变体,它允许在产品环境中切换配置服务器url,因为多个spring.config.import定义而不是相互覆盖:

configserver.import = configserver:${CONFIG_SERVER_URL:https://localhost:8888}
spring.config.import = ${configserver.import}
spring.cloud.config.import-check.enabled = false

为什么spring.cloud.config.import-check.enabled是必要的?因为spring-cloud-config在spring.config.import中至少需要一个前缀为configserver:的值,但在执行此检查之前,不会解析属性替换。因此,spring-cloud-config只将${configserver.import}视为字符串,并将阻止您的应用程序启动。CCD_ 21阻止上述检查。此外,环境变量CONFIG_SERVER_URL可用于覆盖产品环境中的localhost configserver,同时如果configserver.import在测试中设置为空,则仍允许spring.config.import为空字符串。

/咆哮:真是噩梦

经过进一步的研究,我发现了以下github问题:https://github.com/spring-cloud/spring-cloud-config/issues/1877

在我看来,不令人满意的解决方案是如果您需要在测试中覆盖application.yml,则永远不要在CCD_26中放置spring.config.import

特别是对于配置服务器,它应该只放在配置文件特定的配置文件或多文档application.yml的配置文件特定文档中。然后,您可以在生产中激活该概要文件,并在测试和开发阶段使其处于非活动状态。

如果除了测试之外,其他地方都需要配置管理器,那么我认为更好的解决方案是将spring.config.import=configserver:http://localhost:8888放在/scr/main/resources/application.properties中,而不是将.yml文件放在项目的根目录中。然后您只需在使用spring.cloud.config.enabled=false的测试中禁用它。

另一种解决方案(如果您不想完全禁用它)是向导入添加可选关键字(spring.config.import=optional:configserver:http://localhost:8888)(这样您可以将其保留在.yml中,但在测试中会失去禁用它的能力,但它会忽略服务器关闭的事实。

我的解决方案是让spring认为该位置是使用自定义资源加载器加载的。

当该类与前缀为configserver:的导入匹配时,它将返回一个可选资源

package com.roblovelock.spring.cloud.config;
import org.springframework.boot.context.config.*;
import org.springframework.cloud.config.client.ConfigServerConfigDataLocationResolver;
import org.springframework.core.Ordered;
import java.io.IOException;
import java.util.List;
public class NoOpCloudConfig implements ConfigDataLocationResolver<ConfigDataResource>, Ordered {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.hasPrefix(ConfigServerConfigDataLocationResolver.PREFIX);
}
@Override
public List<ConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
return List.of(new NoOpResource());
}
@Override
public List<ConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
return List.of(new NoOpResource());
}
@Override
public int getOrder() {
return -1;
}
public static class NoOpResource extends ConfigDataResource{
private NoOpResource(){
super(true);
}
}
public static class NoOpResourceLoader implements ConfigDataLoader<NoOpResource> {
@Override
public ConfigData load(ConfigDataLoaderContext context, NoOpResource resource) {
return ConfigData.EMPTY;
}
}
}

为了工作,我们需要使用spring.factories注册类。。。

src/test/resources中添加内容为的META-INF/spring.factories文件

org.springframework.boot.context.config.ConfigDataLocationResolver=com.roblovelock.spring.cloud.config.NoOpCloudConfig
org.springframework.boot.context.config.ConfigDataLoader=com.roblovelock.spring.cloud.config.NoOpCloudConfig.NoOpResourceLoader

还要确保您的测试配置包含spring.cloud.config.enabled=false

我的解决方案适用于从Eureka发现ConfigService的情况。属性如下:

spring:
application:
name: app-name
profiles:
active: dev
cloud:
config:
fail-fast: false
discovery:
enabled: true
service-id: app-config-server
uri: http://localhost:8888
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
healthcheck:
enabled: true

此配置使应用程序在配置服务器中查找属性,要求在Eureka中查找其URL。在我找到一组可用的属性之前,此类应用程序中的测试一直处于中断状态。

我找到了一个工作集:

  1. spring.config.import必须在环境SPRING_CONFIG_IMPORT=optional:configserver:http://app-name-in-eureka中精确设置
  2. 指定spring.config.import = ${SPRING_CONFIG_IMPORT:}并不重要
  3. 在测试中指定spring.config.import并不重要
  4. spring.cloud.config.fail-fast.enabled=false-这就是诀窍

最新更新