在测试中使用 Spring @ConfigurationProperties 读取地图

Posted

技术标签:

【中文标题】在测试中使用 Spring @ConfigurationProperties 读取地图【英文标题】:Read a Map with Spring @ConfigurationProperties in test 【发布时间】:2018-07-16 13:32:51 【问题描述】:

根据Spring Boot integration tests doesn't read properties files 的建议,我创建了以下代码,目的是从我的 JUnit 测试中的属性中读取地图。 (我使用的是 yml 格式,并且使用 @ConfigurationProperties 而不是 @Value)

@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(locations="classpath:application-test.yml")
@ContextConfiguration(classes = PropertiesTest.ConfigurationClass.class, PropertiesTest.ClassToTest.class)
public class PropertiesTest 

    @Configuration
    @EnableConfigurationProperties
    static class ConfigurationClass 
    


    @ConfigurationProperties
    static class ClassToTest 
        private String test;

        private Map<String, Object> myMap = new HashMap<>();

        public String getTest() 
            return test;
        

        public void setTest(String test) 
            this.test = test;
        

        public Map<String, Object> getMyMap() 
            return myMap;
        

    

    @Autowired
    private ClassToTest config;


    @Test
    public void testStringConfig() 
        Assert.assertEquals(config.test, "works!");
    

    @Test
    public void testMapConfig() 
        Assert.assertEquals(config.myMap.size(), 1);
    


我的测试配置(在 application-test.yml 中):

test: works!
myMap:
  aKey: aVal
  aKey2: aVal2

奇怪的是,字符串“有效!”已成功从配置文件中读取,但未填充地图。

我错过了什么?

注意:添加地图设置器会导致以下异常:

Caused by: org.springframework.validation.BindException: org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanPropertyBindingResult: 1 errors
Field error in object 'target' on field 'myMap': rejected value []; codes [typeMismatch.target.myMap,typeMismatch.myMap,typeMismatch.java.util.Map,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.myMap,myMap]; arguments []; default message [myMap]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Map' for property 'myMap'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map' for property 'myMap': no matching editors or conversion strategy found]
    at org.springframework.boot.bind.PropertiesConfigurationFactory.checkForBindingErrors(PropertiesConfigurationFactory.java:359)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:276)
    at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:240)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:330)
    ... 42 more

【问题讨论】:

您的地图没有设置器。这就是它不起作用的原因。 Spring 调用 setter 来填充值 类似的bean在没有setter的完整应用程序中成功创建(可以添加map值而不需要设置整个map)。在测试中添加 setter 以异常结束 - 添加了有问题的堆栈跟踪 我猜String, Object 不太合适。您是否尝试过 &lt;String, String&gt; 只是因为它不知道如何将 aVal2 反序列化为 Object 没有。要使 ConfigurationProperties 工作,您需要一个 setter。没有它是行不通的。它可以很容易地验证,只需删除设置器进行测试,看看是否有效。我确信它不会。现在在添加 setter 时遇到异常,我认为这是 Databinder 中的错误。我将值类型更改为字符串,仍然不起作用。删除地图的初始化,仍然没有工作。但是如果我把同样的东西放在 application.properties 文件中它就可以了。它很奇怪。你应该在 github 上提出问题 我在文档中检查并在我的计算机上进行了验证:您是对的。如果没有 setter,它将无法以这种方式工作。但是这个仍然有效:***.com/a/28764090/4068240 老实说,我真的不明白为什么。 【参考方案1】:

在使用调试器度过一段美好时光后, 我相信这是TestPropertySourceUtils.addPropertiesFilesToEnvironment() 中的一个错误/缺失功能:

try 
    for (String location : locations) 
        String resolvedLocation = environment.resolveRequiredPlaceholders(location);
        Resource resource = resourceLoader.getResource(resolvedLocation);
        environment.getPropertySources().addFirst(new ResourcePropertySource(resource));
    

ResourcePropertySource 只能处理.properties 文件,不能处理.yml。 在常规应用中,YamlPropertySourceLoader注册并可以处理.yml

请注意: TestPropertySourceUtils.addPropertiesFilesToEnvironment() 被调用:

org.springframework.test.context.support.DelegatingSmartContextLoader.prepareContext()

(继承自AbstractContextLoader

DelegatingSmartContextLoader 是您在@ContextConfiguration 中未指定加载程序时收到的默认上下文加载程序。 (其实@ContextConfiguration指定了一个接口,但是AbstractTestContextBootstrapper.resolveContextLoader()把它改成了一个具体的类)

为了解决问题,我将配置更改为application-test.properties 并在我的测试中使用了该文件。

test=works!
myMap.aKey: aVal

另一条评论:不需要地图上的二传手:

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-loading-yaml

使用 Spring DataBinder 实用程序绑定到类似的属性 (这就是@ConfigurationProperties 所做的)你需要有一个 类型为 java.util.List(或 Set)的目标 bean 中的属性,而您 要么需要提供一个setter,要么用一个可变的初始化它 值,例如这将绑定到上面的属性

【讨论】:

@TestPropertySource@PropertySource 不支持 yaml 文件。

以上是关于在测试中使用 Spring @ConfigurationProperties 读取地图的主要内容,如果未能解决你的问题,请参考以下文章

spring boot + spring security + jwt + React 不工作

初学Spring Boot

Spring Boot务必了解的注解以及使用场景

Spring Boot务必了解的注解以及使用场景

Spring Security 注销和最大会话数

spring boot importsource怎么设置加载顺序